search home list

Don’t use data attributes to find HTML elements with JS

The HTML5 data attribute

With the introduction of HTML5, JavaScript developers have been blessed with a new customizable and highly flexible HTML tag attribute: the data attribute. Using this attribute to store small chunks of arbitrary data, developers are able to avoid unneccessary AJAX calls and enhance user experience. The W3C specification defines the data attribute as follows:

A custom data attribute is an attribute in no namespace whose name starts with the string “data-“, has at least one character after the hyphen, is XML-compatible, and contains no uppercase ASCII letters.

Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements.

These attributes are not intended for use by software that is independent of the site that uses the attributes.

Every HTML element may have any number of custom data attributes specified, with any value.

W3C specification (Editor’s Draft)

How it should be used

The data attribute is meant to store small amounts of invisible data which is not crucial to the user and might become visible later on. If the data is crucial to the user, it should be presented in a visible and more accessible way. Information contained in the data attribute can only become visible through JavaScript or through the CSS content property.

Let’s look at a simple example of proper usage. On the website of a car rental company we find a list of available cars. Usually available colors are not very important to customers looking to rent a car for a couple of days. That’s going to be our invisible, low priority data.

HTML

<ul class="cars">
 <li data-colors="white, red, yellow">Fiat 500</li>
 <li data-colors="white, black">BMW M3</li>
 <li data-colors="white, black, blue">Audi R8 Coupe</li>
</ul>

If we wanted to show the data to our users when they click on a list element, we could easily achieve this with some JavaScript.

JavaScript

var cars = document.querySelector(".cars");

cars.addEventListener("click", function(e)
{
  alert("Available colors: " + e.target.dataset.colors);
});

Try it yourself

This is all very basic stuff but it clearly demonstrates one way the data attribute can and should be used. Of course there are other situations where the data attribute comes in handy. Say you want to dynamically load a video object. The data attribute would then hold some metadata, i. e. duration, bitrate, codec… you get the point.

How I have used it

It always bugged me that dependencies between styling (CSS) and logic (JS) arise through shared classes. The class cars for example can be used in CSS as well as in JS.

CSS

.cars
{
 list-style-type:none;
}

JavaScript

var list = document.querySelector(".cars");

As we can see, both CSS and JS use the same class to get their job done. But one fine day the CEO of the rental company might decide that it would be totally awesome to extend their offer and include motorcycles for rent. Let’s assume that the integration of motorcycles would require some CSS refactoring. The developer renames the HTML class from cars to vehicles. Through this change both CSS and JS won’t work anymore. Of course, the developer should know and check both, but it would be nice if this wasn’t necessary. With the new data attribute at hand, the idea of using the [data-foo='bar'] selector in JavaScript came along.

HTML

<ul class="vehicles" data-list="cars">
 <li data-colors="white, red, yellow">Fiat 500</li>
 <li data-colors="white, black">BMW M3</li>
 <li data-colors="black, blue">Audi R8 Coupe</li>
</ul>

JavaScript

var cars = document.querySelector("[data-list='cars']");

cars.addEventListener("click", function(e)
{
  alert("Available colors: " + e.target.dataset.colors);
});

Try it yourself

We instantly see the benefit of this approach: renaming the class is not going to affect the functionality of our JavaScript components. In this specific example although, there is the drawback of having a too specific name for the JS module and that just feels wrong. In real life one should try to generalize the name of a JS component to describe its functionality, regardless of the content. A more scalable and robust module could look like this:

HTML

<ul class="vehicles" data-list="interactive">
 <li data-additional-info="white, red, yellow">Fiat 500</li>
 <li data-additional-info="white, black">BMW M3</li>
 <li data-additional-info="black, blue">Audi R8 Coupe</li>
</ul>

The names of the data attributes are no more associated to the content within the module. This way we can use it throughout the whole site.

Trying to decouple CSS and JS by using data attributes is not a new thing. Roy Tomeij adviced to use data attributes to find HTML elements with JS before and I have seen others do the same.

So what’s wrong with it?

Well, for one there are performance issues. Measured in percentages, selecting DOM elements based on data attributes can be significantly slower than classes. In his follow up article, Roy Tomeij analyzes the exact differences in performance. According to him, this is only an issue for very large and complex sites. I think he is right. But then again, performance is always an issue and I don’t consider avoiding data attributes for selection to be premature optimization.

Besides the performance issue, what bothers me more is the fact that the data attribute, by definition, is just not intended to be used that way. Remember the W3C definition?

[…] Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements. […]

W3C specification (Editor’s Draft)

That’s it. Nothing more, nothing less. And that is why at the end of the day I don’t feel comfortable with using data attributes to select DOM nodes. Period.

Use prefixed classes instead!

The trick is to rely on classes for DOM node selection in JavaScript to gain performance but to use a naming convention and strictly separate styling classes from logic classes. Applying this technique, our car rental website could look like this:

HTML

<ul class="vehicles js-interactive-list">
 <li data-additional-info="white, red, yellow">Fiat 500</li>
 <li data-additional-info="white, black">BMW M3</li>
 <li data-additional-info="black, blue">Audi R8 Coupe</li>
</ul>

CSS

.vehicles
{
 list-style-type:none;
 margin:0;
 padding:0;
}

JavaScript

var list = document.querySelector(".js-interactive-list");

list.addEventListener("click", function(e)
{
  alert("Available colors: " + e.target.dataset.additionalInfo);
});

Try it yourself

We see the classes used in JavaScript are prefixed with js-. These classes must never be used for styling! Only that way we can assure that CSS and JS are decoupled and less of a PITA when it comes to refactoring.

Related Articles

Show all articles

21 Responses

  1. Sebastian says:

    I dont think using the data attribute to find an element is a deviation from the w3c definition. The first thing is “there are no more appropriate attributes or elements” and for specific data that corresponds to a specific (and not standard) attribute of the element that may or may not serve as a way to find or filter that (or thoose) specific element(s) is exactly that. Second, it says “intended to store custom data”, everything in the DOM is data and data can be used for many things, included finding elements, just like class and id are data that is used to find and select elements.

    Finally, I highly disagree with your statement “performance is always an issue”. Performance can be an issue in many or most cases, but it’s not always an issue, specially when talking about things executing on the client side (btw, I wanted to see the numbers and your link to roytomeij’s site is broken). The site taking some 400ms extra to finish loading or finish an operation is far from being an important issue, while your solution throws readability out of the window, specially in elements that have several classes or maybe several custom attributes.

  2. krunal says:

    thank you so much.

  3. francisco j cuellar says:

    dom query selector performance is totally overrated, we have clients on 64bit 8g+ RAM, cuad-core 3ghz, they will be fine

  4. Chris Rivera says:

    There is many things wrong with this idea the biggest reason is that css is meant for styling purposes. Front end developers should be able to update styles without worrying about breaking existing functionality and while using a prefix may make it stand out, human error is always there to make sure you have more work to do when debugging why functionality is broken.

    Javascript takes a selector to map to a DOM element, selector being the keyword not class. Selector could be anything from Id, Class or a Data Attribute which already leaves you with too many options.

    Id is not useful because if you need to select more than 1 item it doesn’t work. Classes can be used to select more than 1 item but leaves developers clueless how things are wired up. Data attribute while passing values such as config params is useful it can be used to select 1 or multiple elements. The most extensible selector we can use would be the Data Attribute. To keep code consistent this is my preference. All Javascript i write it tied to data-attributes

    Now lets get down to performance. Data Attribute is the slowest javascript selector since there are more data attributes in a DOM than ids and classes, so for performance reasons we cache the selector for future reference as context

    Example:
    var list = $(“.cars”);
    var $module = $(“[data-module=Cars]”);

    Just by looking at this code it would be easy to assume that data-module=Cars is related to javascript module called Cars. I would go as far to actually name the Javascript Module file ModuleCars.js (This goes to show developer intent which i will touch upon in a sec)

    Since we have saved $module selector we can avoid the performance pitfall of using data attributes to query the full DOM by searching thru a specific context of the DOM.

    Every time you see $({*}), this will look thru the whole DOM to find the element you are looking for. This is the most expensive thing you can do in javascript apart from modifying its DOM. So to minimize the amount of DOM javascript needs to search thru we always reference $module when searching for children elements

    var $list = $module.find(‘[data-cars]’);

    Real Example:
    var $carsList = $(“.cars”);
    var $truckList = $(“.trucks”);
    var $rentalList = $(“.rentals”);

    This will search the full DOM 3 times which is very expensive ( SLOW ) Depending on how large and deep your DOM is performance may vary.

    Performance Example:
    var $carsList = $module.find(“[data-car]”)
    var $truckList = $module.find(“[data-truck]”);
    var $rentalList = $module.find(“[data-rental]”);

    How this is faster:
    When you use a context in this case $module we effectively are minimizing the amount of DOM needed to be processed to find the selectors elements. Instead of searching the full DOM for the specific classes we are looking thru $module element which is already in memory and a fraction of the full DOM

    Developer Intent
    I like to preach about Developer intent because its a huge part of working with a team of developers. If the next developer does not know the intent or needs to read all the code previously written to understand whats going on then there is a lack of efficiency when working with Large teams of developers.

    The use of data attributes shows the developer intent upon scanning as opposed to reading line by line:

    Example:

    Button

    In my opinion is hard to read and complicated as more styling classes are used to generate the effective style.

    Button

    In my opinion is more clear upon quick browsing. I don’t need to decipher all styles to understand whats going on here. classes are specifically used for styling and data attribute specifically tied to javascript functionality.

    A front end developer can change all styles and know that nothing javascript will break the same way they know if they remove a data attribute it will not change the style of the element but will break existing javascript functionality.

    Bootstrap uses data-attribute approach as they are a framework and cannot rely on users to use consistent IDs or class names

    data-toggle=”modal”
    data-toggle=”collapse”
    data-toggle=”dropdown”
    data-toggle=”tab”

    When you see data attributes you know its tied to js functionality no guessing work here.

    Either way you go you can fall into the pitfalls of performance with javascript, if you write bad code and use bad selectors it doesn’t matter what your doing you will end up with non performant javascript anyways so keep your code clean, consistent and make sure your intent is understood or the next developer will read your work and then try to recode it cause they don’t fully understand your thought process.

    My 2 cents.

    • Nicolas Bocquet says:

      Thank you Chris Rivera, I concur, this is exactly what I was thinking reading this article.

    • John says:

      Pretty much my argument all along.

      Ironically the article suggests we are using data-attributes wrong, but isn’t using .js-trigger class selectors equally goes against the grain as what’s it’s intended purpose as for?

      I much prefer to use data-attibs for JS selectors, as it does a couple of things.

      1. FED’s can modify SCSS without concern about breaking JS functionality. Especially with complex systems where backend devs build the JS for callbacks etc.
      2. There’s a clear distinction between what is JS and what is CSS and neither code bases pollute each other.
      3. Transport. Scaleability. I can copy a component, change class names but still have the same JS functionality working.

    • Colin says:

      You say:

      var $carsList = $(“.cars”);
      var $truckList = $(“.trucks”);
      var $rentalList = $(“.rentals”);

      “will search the full DOM 3 times” but then give a great example of how to minimise it with data attributes. That same logic can be applied here surely?

      var $vehicles = $(“.vehicles”);
      var $cars = $vehicles.find(“.cars”);

      and so on…

      Everything else in your well thought out rebuttal I can get behind.

      Thanks 🙂

    • James says:

      “class” attributes – should be for styling (used by CSS only)
      “data” attributes – should be used to store “data” (the clue is in the name)
      “id” attributes – should be used for “identifying” (the clue is in the name)

      So in JS you would target the ID then alter the element – change the class, or grab the data. If you need to target multiple elements then you need to target multiple IDs. This is more robust as when one ID no longer needs targeting you update the JS code and remove it from being targetted instead of removing classes that may also be styling or something else could be targeting it!

      It’s the Javascript you should be changing as it is the authority on controlling the updating of the elements. So each element having an ID and removing an ID being targeted in JS for a specific element means you’ve changed the authority code from performing an action.
      If you target classes instead, what if the class used to identify the element in JS is also a style? Even if not, removing the class from the HTML element to make JS not update it anymore is the wrong way around in my opinion.

      This approach means FE devs can have at it with classes for their styling, and JS devs can manage targetting the element without fear as no-one should be changing the ID once set.

  5. kofifus says:

    I disagree … “what bothers me more is the fact that the data attribute, by definition, is just not intended to be used that way” – true but true for css classes as well. The problem with using classes for id’s is that classes have the ‘side effect’ of well being classes – they style their elements. So if you use a class ‘id’ (ie ‘rebbox’) that happen to be styled by another third party css library you loaded, your element will suddenly get styled according to that class ! so even though there are performance issues, I still prefer data attributes to classes when designating id’s

    • mopsyd says:

      I agree with this. It is also a frequent occurrence for 3rd party javascript to arbitrarily mutate, strip, or alter classes on elements when they have styling considerations, which makes classes extremely likely to collide, not be present as expected, or otherwise cause frequent mishaps and collisions with 3rd party libraries. I personally prefer to put a unique data attribute on interesting dom elements, grab all of them with a querySelectorAll statement to obtain persistent HTMLElement references to them, and then sort them as needed from there. That way alterations to the dom do not interfere with the reference pointers, there is no chance of causing accidental styling derps, and the likelihood that another library is using your unique data attribute is very small. I think that is worth the extra tenth of a second it takes to run one querySelectorAll statement, as it insures that the ongoing performance of the page is not disrupted for the sake of shaving microseconds off of load time.

      • Oskar says:

        Using data attributes does not prevent multiple such third party libraries from interfering with each other, and such occurrences indicate a poorly written library. Additionally, if it’s unlikely that some other library collides with your selected unique data attribute, it would probably be equally unlikely if you picked a “likely-to-be-unique” prefix for all your own classes. Which is also what the libraries should have done to facilitate reuse, naturally. 🙂

  6. Matt says:

    I have a suspicion that, in most cases, and in the case of most developers, making our selectors more specific will be a more practical concern than whether we use data- attributes or class attributes.
    eg: document.querySelector() vs. myContainingElement.querySelector()
    here’s a jsperf: http://jsperf.com/generictestjsperf

  7. Colin Pear says:

    I was just getting ready to disagree with you but after reading the W3C spec i think you may be right.

  8. Karsten says:

    Perhaps something to think about. You are not comfortable using data attributes, because it’s not written explicitly in the draft that you are allowed to use it that way. But using class that way isn’t that a bigger deviation from the W3C specification?

  9. Travis says:

    Why not just use the ID attribute for each element? This would also keep each element entirely unique.

    • When using ID’s you can only target one single element with JS, for example manipulating several LI’s at a time is much easier using class=”js-list–item” for all of them instead of targeting them one by one with id=”list–item1″, id=”list–item2″ etc.

  10. Matteo says:

    Hi, I was working with data- attribute and I was searching what is the best way of select html with this kind of selector. I created a fund list and filtering tool. The column contains different blocks of filter, below one filter category box:

    Type

    Fund 1

    Fund 2

    I used ‘data-filter’ because I need a name reference of the filter, If I use a class I cannot create a loop and get the category name because inside the class i could use others class.

    What is it the best way of store the filter name and select it by js.
    thanks bye

  11. I use prefixes: ‘trigger-‘ for pre-existing classes and ‘js-‘ for classes added by js. Makes for a bit easier debugging too since you tell, at a glance, if a class is being added or was already there.

What do you think?