Introduction
HTML elements can have attributes on them that are used for anything from accessibility information to stylistic control.
<!-- We can use the `class` for styling in CSS, and we've also make this into a landmark region -->
<div class="names" role="region" aria-label="Names"></div>
What is discouraged is making up your own attributes, or repurposing existing attributes for unrelated functionality.
<!-- `highlight` is not an HTML attribute -->
<div highlight="true"></div>
<!-- `large` is not a valid value of `width` -->
<div width="large">
There are a variety of reasons this is bad. Your HTML becomes invalid, which may not have any actual negative consequences, but robs you of that warm fuzzy valid HTML feeling. The most compelling reason is that HTML is a living language and just because attributes and values that don’t do anything today doesn’t mean they never will.
Good news though: you can make up your own attributes. You just need to prefix them with data-*
and then you’re free to do what you please!
Syntax
It can be awfully handy to be able to make up your own HTML attributes and put your own information inside them. Fortunately, you can! That’s exactly what data attributes are. They are like this:
<!-- They don't need a value -->
<div data-foo></div>
<!-- ...but they can have a value -->
<div data-size="large"></div>
<!-- You're in HTML here, so careful to escape code if you need to do something like put more HTML inside -->
<li data-prefix="Careful with HTML in here."><li>
<!-- You can keep dashing if you like -->
<aside data-some-long-attribute-name><aside>
Data attributes are often referred to as data-*
attributes, as they are always formatted like that. The word data
, then a dash -
, then other text you can make up.
data
attribute alone?
Can you use the <div data=""></div>
It’s probably not going to hurt anything, but you won’t get the JavaScript API we’ll cover later in this guide. You’re essentially making up an attribute for yourself, which as I mentioned in the intro, is discouraged.
What not to do with data attributes
Store content that should be accessible. If the content should be seen or read on a page, don’t only put them in data attributes, but make sure that content is in the HTML content somewhere.
<!-- This isn't accessible content -->
<div data-name="Chris Coyier"></div>
<!-- If you need programmatic access to it but shouldn't be seen, there are other ways... -->
<div>
<span class="visually-hidden">Chris Coyier</span>
</div>
Here’s more about hiding things.
Styling with data attributes
CSS can select HTML elements based on attributes and their values.
/* 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"] { }
This can be compelling. The predominant styling hooks in HTML/CSS are classes, and while classes are great (they have medium specificity and nice JavaScript methods via classList
) an element either has it or it doesn’t (essentially on or off). With data-*
attributes, you get that on/off ability plus the ability to select based on the value it has at the same specificity level.
/* Selects if the attribute is present at all */
[data-size] { }
/* Selects if the attribute has a particular value */
[data-state="open"],
[aria-expanded="true"] { }
/* "Starts with" selector, meaning this would match "3" or anything starting with 3, like "3.14" */
[data-version^="3"] { }
/* "Contains" meaning if the value has the string anywhere inside it */
[data-company*="google"] { }
The specificity of attribute selectors
It’s the exact same as a class. We often think of specificity as a four-part value:
inline style, IDs, classes/attributes, tags
So a single attribute selector alone is 0, 0, 1, 0. A selector like this:
div.card[data-foo="bar"] { }
…would be 0, 0, 2, 1. The 2 is because there is one class (.card
) and one attribute ([data-foo="bar"]
), and the 1 is because there is one tag (div
).
Attribute selectors have less specificity than an ID, more than an element/tag, and the same as a class.
Case-insensitive attribute values
In case you’re needing to correct for possible capitalization inconsistencies in your data attributes, the attribute selector has a case-insensitive variant for that.
/* Will match
<div data-state="open"></div>
<div data-state="Open"></div>
<div data-state="OPEN"></div>
<div data-state="oPeN"></div>
*/
[data-state="open" i] { }
It’s the little i
within the bracketed selector.
Using data attributes visually
CSS allows you to yank out the data attribute value and display it if you need to.
/* <div data-emoji="✅"> */
[data-emoji]::before {
content: attr(data-emoji); /* Returns '✅' */
margin-right: 5px;
}
Example styling use-case
You could use data attributes to specify how many columns you want a grid container to have.
<div data-columns="2"></div>
<div data-columns="3"></div>
<div data-columns="4"></div>
Accessing data attributes in JavaScript
Like any other attribute, you can access the value with the generic method getAttribute
.
let value = el.getAttribute("data-state");
// You can set the value as well.
// Returns data-state="collapsed"
el.setAttribute("data-state", "collapsed");
But data attributes have their own special API as well. Say you have an element with multiple data attributes (which is totally fine):
<span
data-info="123"
data-index="2"
data-prefix="Dr. "
data-emoji-icon="🏌️♀️"
></span>
If you have a reference to that element, you can set and get the attributes like:
// Get
span.dataset.info; // 123
span.dataset.index; // 2
// Set
span.dataset.prefix = "Mr. ";
span.dataset.emojiIcon = "🎪";
Note the camelCase usage on the last line there. It automatically converts kebab-style attributes in HTML, like data-this-little-piggy
, to camelCase style in JavaScript, like dataThisLittlePiggy
.
This API is arguably not quite as nice as classList
with the clear add
, remove
, toggle
, and replace
methods, but it’s better than nothing.
You have access to inline datasets as well:
<img src="spaceship.png"
data-ship-id="324" data-shields="72%"
onclick="pewpew(this.dataset.shipId)">
</img>
JSON data inside data attributes
<ul>
<li data-person='
{
"name": "Chris Coyier",
"job": "Web Person"
}
'></li>
</ul>
Hey, why not? It’s just a string and it’s possible to format it as valid JSON (mind the quotes and such). You can yank that data and parse it as needed.
const el = document.querySelector("li");
let json = el.dataset.person;
let data = JSON.parse(json);
console.log(data.name); // Chris Coyier
console.log(data.job); // Web Person
JavaScript use-cases
The concept is that you can use data attributes to put information in HTML that JavaScript may need access to do certain things.
A common one would have to do with database functionality. Say you have a “Like” button:
<button data-id="435432343">♡</button>
That button could have a click handler on it which performs an Ajax request to the server to increment the number of likes in a database on click. It knows which record to update because it gets it from the data attribute.
Specifications
- Selectors Level 4 (Working Draft)
- Selectors Level 3 (Recommended)
- Selectors Level 2, Revision 1 (Initial Definition)
Browser support
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
7 | 6 | 11 | 12 | 5.1 |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
126 | 127 | 3 | 5.0-5.1 |
i’m assuming you could also do
I don’t think so right at this moment. I think
attr()
only works for thecontent
property and it maybe has something to do with types (e.g. we need a way to specify that an attribute is a number or length or something and not a string).JSON data (or any other data) inside data attributes ….
I use the data-attribute to transport data from a backend SQL-DB via PHP to my web clients. As I can not make any assumptions about the data I have to transport, I encode64 it before enclosing it as a value in ‘ ‘ or ” ” for the data-attribute. This way I avoid any escape orgy to make sure the data arrives at the client. At the client when consuming the data I use decode64, to get the data back.
Regards
IMO this is one step too much already. Especially when you’re working with PHP, just using
htmlspecialchars()
on the data (possibly with theENT_QUOTES
flag) is enough to guarantee you’re not getting into any escaping trouble.This has the advantage of not having to do any decoding on the JavaScript side — just accessing
element.dataset.name
will yield the correct data with all the encoded characters already decoded by the browser.Hi Florian
this is true, looks like overkill, however I feel beeing on the ‘safer’ side because I am not altering the original data, I just transport the data as is, making no assumption about its usage.
I, on the backend, do not know if the client will render the data or use it in a regex, pass it on to some other process or whatever …
Cheers
Wow, great post. I love your complete guides. As a self-taught dev I often find that I knew like 50% of each section of these guides just by experience, but getting the full picture helps so much!
I had no idea there was a JavaScript API for these or that you had so many options for selecting within CSS. Very cool.
This is a really great article. Thanks for writing it up. And I love the green lights at the bottom.
It should probably be pointed out that case-insensitive attribute values are not supported by Internet Explorer or legacy Edge.
Contrary to what Can I use says it does work in Chromium Edge
https://caniuse.com/#feat=mdn-css_selectors_attribute_case_insensitive_modifier
This is a really useful guide, thank you Chris!
Immediately inspired, I went to try and make a CSS system where a user can use the data attribute to specify the rows they want (just like in your example), but then use attr(data-columns) within grid-template-columns to give me something like: grid-template-columns: repeat(attr(data-columns), 1fr);
I thought that would be a really nice way to quickly define a grid from the HTML, but unfortunately that does not work, do you know if there’s a way to do something similar without having to write the CSS for all possible options or am I missing something?
Awesome write up. Thank you for sharing!
Cómo puedo colorear la sintaxis de mi código fuente, así como aparece en vuestra página web , para dar ejemplos.
https://prismjs.com
Is there any way to get a javascript or jQuery click event on an element with an attribute starting with a certain text? e.g “data-gtm-event-“
Another use-case:
control error messages via js.
Like the label’s for attribute targeting an input with the specified id, you can populate any error messages for the id it references.
I’ll also add an
aria-live
attribute to<p>
to tell Screen Readers of any changes.With the use of the data attribute is it possible to improve the SEO of a site or is it irrelevant?
I am having trouble making this code work:
<div class="box" data-width="150">Some content..</div>
<style>
.box{
position:absolute;
left: attr(data-width px);
}
</style>
is it possible to set the value of “left” propriety this way ?
Yeah, that’s because
attribute()
is unsupported by theleft
property. That’d have to go on thecontent
property of a pseudo element to spit the number out.attr(data-something) works in ::after{} and ::befor{} .
And the attrebit you can write in the content like this :
div[data-smth]
[data-smth]::after or before::{
content: attr(data-smth);
}
if you need like your style just use js in js not it is Difficult their
Hey just wondering if there are any other selectors i was hoping for greater than but even not equals would be great