Data Attributes: Storing Your Own Data in Markup
A legitimate way to attach custom information to an element, readable from CSS and JavaScript, without inventing invalid attributes.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Concept
Sometimes an element needs to carry information that no standard attribute expresses: a product identifier, a state, a configuration value a script will read. Inventing an attribute is invalid and risks colliding with a future standard one.
Data attributes are the sanctioned answer. Any attribute whose name begins with data- is valid, is ignored by the browser, and is available to your CSS and JavaScript.
Syntax
<article data-id="1043"
data-category="lamps"
data-in-stock="true"
data-price="4999">
<h3>Brass table lamp</h3>
</article>Naming rules: after the data- prefix, at least one character, lower case, no capital letters, and no colon at the start. Use hyphens to separate words.
Reading them in JavaScript
const card = document.querySelector("article");
// dataset converts data-in-stock to inStock
card.dataset.id; // "1043"
card.dataset.category; // "lamps"
card.dataset.inStock; // "true"
// writing back updates the attribute in the DOM
card.dataset.category = "lighting";
delete card.dataset.price;
// the older, longer way
card.getAttribute("data-id");Two things catch people out. Hyphenated names become camel case in dataset, so data-in-stock is dataset.inStock. And every value is a string: dataset.inStock is the string "true", which is truthy even when it reads "false".
// wrong: any non empty string is truthy
if (card.dataset.inStock) { ... }
// right
if (card.dataset.inStock === "true") { ... }
const price = Number(card.dataset.price);Using them in CSS
/* select by value */
[data-status="pending"] { border-left: 3px solid #f59e0b; }
[data-status="paid"] { border-left: 3px solid #16a34a; }
/* select by presence */
[data-featured] { box-shadow: 0 0 0 2px #1e3a8a; }
/* substring matching */
[data-category^="light"] { } /* starts with */
[data-tags~="sale"] { } /* one of a space separated list */
/* print the value */
.badge::after {
content: attr(data-count);
}Using a data attribute for state rather than toggling classes is often cleaner, because one attribute can hold many mutually exclusive values where classes would need adding and removing individually.
// one line, and the previous state is replaced
row.dataset.status = "paid";Example: a filterable list
<div class="filters">
<button type="button" data-filter="all">All</button>
<button type="button" data-filter="lamps">Lamps</button>
<button type="button" data-filter="vessels">Vessels</button>
</div>
<ul class="products">
<li data-category="lamps" data-price="4999">Brass table lamp</li>
<li data-category="vessels" data-price="2400">Copper jug</li>
<li data-category="lamps" data-price="7250">Hanging lantern</li>
</ul>document.querySelector(".filters").addEventListener("click", (event) => {
const filter = event.target.dataset.filter;
if (!filter) return;
document.querySelectorAll(".products li").forEach((item) => {
const show = filter === "all" || item.dataset.category === filter;
item.hidden = !show;
});
});Example: configuring a component from markup
<div class="chart"
data-endpoint="/api/ridership"
data-type="line"
data-refresh="60"></div>document.querySelectorAll(".chart").forEach((el) => {
renderChart(el, {
endpoint: el.dataset.endpoint,
type: el.dataset.type,
refreshSeconds: Number(el.dataset.refresh),
});
});The configuration lives beside the element it configures, which is easier to follow than a separate lookup table keyed by id.
What not to put in one
- Anything sensitive. Data attributes are in the page source and fully editable by the reader. A price, a role, a user id or a discount that the server later trusts is a real vulnerability.
- Content. If a reader needs to see it, it belongs in the text, not in an attribute. Text in an attribute is not selectable, not searchable, not translated and often not announced.
- Accessibility information. That is what ARIA attributes are for; assistive technology does not read
data-*. - Large blobs. A serialised JSON object in an attribute is a maintenance problem. Fetch the data instead.
Important rules
- Names are lower case; hyphenated names become camel case in
dataset. - Values are always strings.
- Any element may carry any number of them.
- They are valid HTML and are ignored by the browser.
- Assistive technology does not expose them.
- They are visible and editable in DevTools.
Common mistakes
- Treating
"false"as falsy. - Forgetting to convert a numeric value with
Number(). - Writing
data-userIdand being surprised that it becomesdata-userid. - Storing content that should be visible text.
- Storing a price and trusting it on the server.
- Using them for accessibility instead of ARIA.
- Inventing a bare attribute such as
userid="12"instead of prefixing it.
Best practices
- Prefix custom attributes with
data-, always. - Use them for state a stylesheet needs to react to.
- Convert types explicitly on read.
- Keep them short and store identifiers rather than values.
- Prefer a data attribute to a class when a value is one of several mutually exclusive states.
- Never trust them on the server.
Practice
- Build a list of items with
data-categoryand filter it with a single click handler. - Style three states of a row using one
data-statusattribute and CSS attribute selectors. - Read a numeric data attribute, add ten to it, and explain what happens if you forget to convert it.
- Print a count into a badge using
content: attr(...).