Data Attributes
Introduction
Sometimes you need to attach extra information to an element—IDs for JavaScript, hooks for CSS, or analytics labels—without inventing invalid attributes. HTML data-* attributes solve that. This chapter explains naming rules, reading values from JavaScript, common use cases, and what not to store in markup.
Prerequisites
- Linking CSS and JavaScript
- HTML syntax basics
- Basic comfort with
querySelector(from the JavaScript track)
What Are data-* Attributes?
Any attribute starting with data- is a custom data attribute:
<article
class="product-card"
data-product-id="sku-42"
data-price="29.99"
data-currency="USD"
>
<h2>Wireless Mouse</h2>
<button type="button" data-action="add-to-cart">Add to cart</button>
</article>Rules:
- must start with
data- - use lowercase letters, numbers, hyphens after
data- - no spaces in the attribute name
- value is always a string in HTML
Valid:
data-user-id="1001"
data-scroll-target="main-content"Invalid:
dataUserId="1001"
data-user id="1001"Naming Convention
Use kebab-case after data-:
data-order-id="ORD-2026-001"
data-is-active="true"
data-max-items="5"In JavaScript, dataset converts to camelCase:
| HTML attribute | element.dataset property |
|---|---|
data-order-id | orderId |
data-is-active | isActive |
data-max-items | maxItems |
Reading with dataset
<button type="button" id="save" data-form-id="contact-form">Save</button>const saveBtn = document.querySelector("#save");
// Read custom data
console.log(saveBtn.dataset.formId);
// "contact-form"
// Write custom data
saveBtn.dataset.saved = "true";dataset only includes data-* attributes on that element.
Passing Configuration to JavaScript
A tabs UI without stuffing logic into HTML:
<div class="tabs" data-active-tab="overview">
<button type="button" data-tab="overview" aria-selected="true">Overview</button>
<button type="button" data-tab="specs">Specs</button>
<button type="button" data-tab="reviews">Reviews</button>
</div>
<section id="overview">...</section>
<section id="specs" hidden>...</section>
<section id="reviews" hidden>...</section>const tabButtons = document.querySelectorAll("[data-tab]");
tabButtons.forEach((button) => {
button.addEventListener("click", () => {
const tabName = button.dataset.tab;
console.log("Switch to", tabName);
// show matching section...
});
});HTML holds what was clicked; JavaScript handles how to react.
CSS Hooks with Data Attributes
Style based on state without extra classes:
<button type="button" data-state="loading" disabled>Loading...</button>button[data-state="loading"] {
opacity: 0.7;
cursor: wait;
}Compare with classes—both work. data-state emphasizes state metadata; class emphasizes styling hooks. Teams often use both deliberately.
Analytics and Tracking Labels
<a
href="/signup"
data-analytics-event="cta_click"
data-analytics-location="hero"
>
Start free trial
</a>JavaScript reads attributes and sends events to analytics tools. Keep values small and non-sensitive.
Warning
No Secrets in data-*
Anyone can view HTML source. Never put API keys, tokens, or private user data in data-* attributes.
JSON in Data Attributes (Use Sparingly)
You can store JSON as a string:
<div
id="chart"
data-series='{"labels":["Jan","Feb"],"values":[10,14]}'
></div>const chart = document.querySelector("#chart");
const series = JSON.parse(chart.dataset.series);Problems:
- quoting is error-prone
- large JSON bloats HTML
- must escape carefully
Prefer:
- small primitives in
data-* - fetch JSON from an API or embed in
<script type="application/json">for larger payloads
What Not to Store in HTML
Avoid putting in data-* (or any attribute):
- passwords or session tokens
- full user profiles
- large configuration blobs
- business logic that should live on the server
- content that should be visible text (put it in the page body)
HTML is public. Treat attributes as hints for UI behavior, not a database.
data-* vs id vs class
| Tool | Best for |
|---|---|
id | Unique element, anchor targets, ARIA references |
class | Styling and reusable groups |
data-* | Custom metadata for JS/CSS behavior |
Example together:
<button
type="button"
id="checkout-btn"
class="btn btn-primary"
data-step="payment"
data-testid="checkout-submit"
>
Pay now
</button>data-testid is popular in automated tests (not a formal HTML spec type, but widely used).
Mini Example: Filter List
<ul class="tag-list">
<li><button type="button" data-filter="all" class="active">All</button></li>
<li><button type="button" data-filter="html">HTML</button></li>
<li><button type="button" data-filter="css">CSS</button></li>
</ul>
<ul class="course-list">
<li data-category="html">HTML Basics</li>
<li data-category="html">Semantic Layout</li>
<li data-category="css">CSS Intro</li>
</ul>FAQ
Are data-* attributes valid in HTML5?
Yes. They are part of the standard for custom metadata.
Do search engines use data-* for ranking?
Generally no for arbitrary data. Visible content and standard meta tags matter more.
Can I use data- in CSS selectors?
Yes: [data-filter="html"], .card[data-active="true"].
Does dataset update the HTML attribute?
Setting element.dataset.foo = "bar" updates data-foo in the DOM. Inspecting Elements shows the change.
What comes next?
Browser DevTools for HTML — inspect, edit, and debug pages in the browser.