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

What Are data-* Attributes?

Any attribute starting with data- is a custom data attribute:

html
<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:

html
data-user-id="1001"
data-scroll-target="main-content"

Invalid:

html
dataUserId="1001"
data-user id="1001"

Naming Convention

Use kebab-case after data-:

html
data-order-id="ORD-2026-001"
data-is-active="true"
data-max-items="5"

In JavaScript, dataset converts to camelCase:

HTML attributeelement.dataset property
data-order-idorderId
data-is-activeisActive
data-max-itemsmaxItems

Reading with dataset

html
<button type="button" id="save" data-form-id="contact-form">Save</button>
js
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:

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>
js
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:

html
<button type="button" data-state="loading" disabled>Loading...</button>
css
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

html
<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:

html
<div
  id="chart"
  data-series='{"labels":["Jan","Feb"],"values":[10,14]}'
></div>
js
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

ToolBest for
idUnique element, anchor targets, ARIA references
classStyling and reusable groups
data-*Custom metadata for JS/CSS behavior

Example together:

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

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