A data attribute, also written as a data-* attribute, is a custom HTML attribute whose name begins with `data-` and which stores private, page-specific information on an element, for example data-product-id="482". In HTML it is the standard, valid way to attach extra values for scripts and styles without inventing non-standard attributes or abusing classes and IDs.
Data attributes are ignored by the browser’s rendering and accessibility layers: they do not change how an element looks or what it means. Their value lies in being readable from JavaScript through the dataset property and selectable from CSS with attribute selectors, which makes them a lightweight bridge between markup, behavior and presentation.
Syntax and naming rules
The HTML Living Standard defines a custom data attribute as any attribute whose name starts with `data-` followed by at least one character. The name must be XML-compatible, must not contain uppercase ASCII letters, and must not begin with xml after the prefix. Values are always plain strings; numbers, booleans and lists must be encoded as text and parsed by the script that reads them.
The table below shows how attribute names in markup map to properties in the DOM.
| Attribute in HTML | Property in JavaScript | Value type read |
|---|---|---|
| `data-id` | element.dataset.id | String |
| `data-user-id` | element.dataset.userId | String |
| `data-max-items` | element.dataset.maxItems | String, parse to number |
| `data-is-open` | element.dataset.isOpen | String such as true or false |
| `data-config` | element.dataset.config | String, often JSON to parse |
The conversion follows a fixed rule: the `data-` prefix is removed and every hyphen followed by a lowercase letter becomes the uppercase version of that letter, producing camelCase. Setting element.dataset.maxItems = 10 performs the reverse and writes data-max-items="10" into the DOM.
Reading and writing data attributes
Scripts can access data attributes in two ways. The `dataset` property returns a live DOMStringMap with camelCase keys, while `getAttribute` and `setAttribute` use the full hyphenated name. Both read and change the same underlying attribute, so the choice is mainly about readability and consistency within a codebase.
CSS can use the same attributes in two ways. Attribute selectors such as [data-state="open"] apply styles based on a value, which is a clean way to express component state. The attr() function can display a value in generated content, for example a tooltip label in a pseudo-element, although broad support for using attr() in other properties is still limited.
| Technique | Example | Typical purpose |
|---|---|---|
| dataset read | button.dataset.action | Choose behavior in an event handler |
| dataset write | panel.dataset.state = "open" | Expose component state to CSS |
| Attribute selector | [data-variant="primary"] | Style variants without extra classes |
| Query selector | querySelectorAll("[data-track]") | Find elements for analytics or tests |
| attr() in content | content: attr(data-label) | Show a value in generated content |
Data attributes compared with other ways to store information
Data attributes overlap with classes, IDs, ARIA attributes and JavaScript objects, but each mechanism has a distinct responsibility. Mixing them leads to brittle code, for example styles breaking when a test hook is renamed, or accessibility depending on a value that assistive technology never reads.
| Mechanism | Primary role | Use it for | Avoid it for |
|---|---|---|---|
| Data attribute | Private custom values | Script hooks, state, test IDs | Meaning or accessibility |
| Class | Styling categories | Reusable visual styles | Storing values |
| ID | Unique identification | Fragment links, label association | Repeated elements |
| ARIA attribute | Accessibility semantics | States such as aria-expanded | Arbitrary application data |
| JavaScript state | In-memory data | Large or sensitive data | Values CSS must react to |
A practical pattern combines them: an accordion button uses `aria-expanded` to communicate state to screen readers and a `data-state` attribute, or the ARIA attribute itself, for styling. When an ARIA attribute already expresses the state, styling it directly avoids duplicated and potentially inconsistent state.
Common uses in web development
Data attributes are widespread because they keep configuration close to the markup that needs it. Server-rendered pages can embed values that client scripts pick up without extra network requests, and frameworks and libraries use them for initialization and hydration.
- Behavior hooks:
data-action="delete"lets one event listener handle many buttons through event delegation. - Test selectors:
data-testidgives end-to-end tests a stable target that does not change with styling. - Analytics:
data-trackattributes mark elements whose interactions should be recorded. - Component configuration: carousels, modals and charts read options such as interval or theme from data attributes.
- State for styling: values such as
data-state="loading"drive CSS transitions without class juggling.
Limitations and mistakes to avoid
Because data attributes are visible in the page source and the DOM, they must never contain secrets such as tokens, personal data or internal pricing rules. They are also not indexed for meaning by search engines and not exposed to assistive technology, so content that users need must appear as visible text or proper semantic HTML.
| Problem | Consequence | Fix |
|---|---|---|
| Sensitive data in attributes | Exposed to anyone viewing source | Keep it on the server or in memory |
| Uppercase in names | Lowercased by the parser, lookup fails | Use lowercase, hyphen-separated names |
| Large JSON blobs | Bloated HTML, slow parsing | Use a script data block or an API |
| Meaning only in data attributes | Invisible to screen readers | Use semantic elements and ARIA |
| Treating values as numbers | String comparison bugs | Parse explicitly before calculating |
The simple explanation
A data attribute is like a small sticky note attached to a part of a web page. Visitors do not see it, but the code running the page can read it. For example, a button might carry a note with the number of the product it adds to the cart.
This keeps information right where it is needed. Developers do not have to keep it somewhere else. The important rule is to never put secrets on these notes. Anyone who opens the page code can read them.
How it is used in practice
An online bookstore lists 24 books per page, each with an Add to cart button. Instead of attaching 24 separate event listeners, the developer gives every button a data-action="add-to-cart" and a data-isbn value, then adds one delegated click listener on the product list that reads event.target.dataset.isbn and sends it to the cart API.
While the request runs, the script sets data-state="loading" on the button, and a CSS rule targeting that value shows a spinner and disables pointer events. The QA team targets the same buttons through stable data-testid values. The result is less JavaScript, clear state styling and end-to-end tests that no longer break after visual redesigns.