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

14 Responses

  1. 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.

  2. 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

  3. 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.

  4. 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?

  5. Colin Pear says:

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

  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. 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

  8. 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.

  9. 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

  10. krunal says:

    thank you so much.

What do you think?