Custom attributes without the guilt: a guide to data-*

HTML attributes serve many purposes, from accessibility to styling. But what about the cases where you need to stash your own metadata on an element? Inventing ad-hoc attributes is tempting but risky; the HTML spec is a living document, and an attribute you repurpose for your own needs today could take on a native meaning tomorrow, breaking your assumptions.

The escape hatch HTML provides is the data-* family of attributes. You are free to define any attribute as long as it begins with data- followed by a dash and your chosen name.

<!-- They don't need a value -->
<div data-foo></div>
<!-- ...but they can have a value -->
<div></div>
<!-- You're in HTML here, so careful to escape code if you need to do something like put more HTML inside -->
<li><li>
<!-- You can keep dashing if you like -->
<aside data-some-long-attribute-name><aside>

Note that using the attribute data on its own (without a suffix) is not the same thing—it won't give you access to the dedicated JavaScript API for data-*, and you're effectively back to inventing a non-standard attribute.

What to keep out of data attributes

Data attributes should not be the sole home for content that needs to be accessible to users or assistive technology. If the information should be visible or readable on the page, it belongs in the HTML content itself, not hidden away exclusively in an attribute.

Styling with data attributes in CSS

CSS already has a robust mechanism for selecting elements based on attributes and their values. This works seamlessly with data-*. While classes are the standard styling hook, they offer a binary has it or doesn't state. Attribute selectors give you that same on/off capability plus the ability to differentiate elements by the specific value of the attribute, all at the same specificity weight.

/* Select any element with this data attribute and value */
[data-size="large"] {
  padding: 2rem;
  font-size: 125%;
}
/* You can scope it to an element or class or anything else */
button[data-type="download"] { }
.card[data-pad="extra"] { }

The specificity of attribute selectors

In the specificity model of inline styles, IDs, classes, and tags, attributes sit in the same column as classes. A single attribute selector alone carries a specificity of 0, 0, 1, 0. By comparison, a selector combining a class, an attribute, and an element would be 0, 0, 2, 1: the two points for the class and attribute, and one for the tag.

div.card[data-foo="bar"] { }

Case-insensitive attribute values and visual output

If your data values might come in inconsistent capitalization, the attribute selector includes a case-insensitive flag. Appending a small i inside the brackets instructs the browser to match values regardless of case:

/* Will match
<div></div>
<div></div>
<div></div>
<div></div>
*/
[data-state="open" i] { }

CSS can also read data attribute values and display them directly, pulling content like a configuration string or state label straight out of the markup:

/* <div> */
[data-emoji]::before {
  content: attr(data-emoji); /* Returns '✅' */
  margin-right: 5px;
}

This is handy in layout logic, for example letting a component specify its own grid column count:

<div></div>
<div></div>
<div></div>

Using data attributes in JavaScript

As with any attribute, the generic getAttribute method will read a data attribute's value. But data-* attributes come with their own dedicated API: the dataset property. For an element with several data attributes, you can both read and write them directly:

<span 
  
  
 
 
></span>
// Get
span.dataset.info; // 123
span.dataset.index; // 2
// Set
span.dataset.prefix = "Mr. ";
span.dataset.emojiIcon = "🎪";

The API performs a kebab-case to camelCase conversion: an attribute named data-this-little-piggy becomes dataThisLittlePiggy in the dataset object. While this API isn't as expressive as classList with its add, remove, toggle, and replace methods, it covers the basic use cases. The inline dataset object is also directly accessible and writable.

Storing JSON payloads

Because a data attribute is just a string, you can store structured data in it as long as you format it as valid JSON. You can parse it on read and use it to configure your scripts:

<ul>
  <li data-person='
    {
      "name": "Chris Coyier",
      "job": "Web Person"
    }
  '></li>
</ul>

Beyond that, data-* is a handy place for JavaScript to look up the context it needs when handling user interactions. For example, a "Like" button can carry its database record ID in a data attribute. A click handler can read that ID and use it in an Ajax request without needing to query or parse the DOM elsewhere:

<button>♡</button>

W3C specifications and browser support

The attribute selector has been defined across three generations of the CSS specification: it was initially defined in the Selectors Level 2, Revision 1 recommendation, and later refined in the Selectors Level 3 recommendation and the current Selectors Level 4 working draft.

Browser support for the dataset API, as documented by Caniuse, is comprehensive across both desktop and mobile platforms.

ChromeFirefoxIEEdgeSafari
7611125.1
Android ChromeAndroid FirefoxAndroidiOS Safari
15115335.0-5.1