|$ curl https://forge-ai.dev/api/markdown?path=docs/html/data-attributes
$cat docs/data-attributes.md
updated This week·25 min read·published

Data Attributes

HTMLAttributesIntermediateIntermediate
Introduction

Data attributes are custom attributes on any HTML element that store extra information without non-standard attributes, hidden classes, or JavaScript variables. They follow the data-* naming convention and provide a clean, standard way to embed custom data in HTML.

Introduced in HTML5, data attributes solved a common problem: developers were using class or rel attributes to store application data, which polluted semantics and broke standards compliance. Data attributes provide a dedicated mechanism for custom data that is valid HTML, accessible via JavaScript and CSS, and completely ignored by search engines and assistive technologies by default.

Syntax & Naming Rules

A data attribute name consists of the prefix data- followed by a lowercase string with words separated by hyphens. The attribute value is always a string — numbers, booleans, and objects must be serialized as strings and parsed when accessed.

RuleValidInvalid
Prefixdata-* (always lowercase)Data-*, DATA-*
Namingdata-user-id, data-configdata-userId (no camelCase in HTML)
Charactersdata-my-data_2data-my data, data-<XML>
Value typedata-count="42"data-count=42 (unquoted numeric)
Empty valuesdata-active=""data-active (bare boolean attribute)
data-attribute-syntax.html
HTML
1<!-- Valid data attribute names -->
2<div data-user-id="42"></div>
3<div data-user-role="admin"></div>
4<div data-config-theme="dark"></div>
5<div data-timestamp="1720329600000"></div>
6<div data-items="[1,2,3]"></div>
7<div data-active="true"></div>
8<div data-empty=""></div>
9
10<!-- Serialize complex data as JSON strings -->
11<div
12 data-product='{"id":42,"name":"Widget","price":9.99}'
13 data-attributes='["red","blue","green"]'
14></div>
15
16<!-- Common pattern: data attribute with boolean presence -->
17<button data-submitted disabled>
18 Already Submitted
19</button>
20
21<!-- Data attributes work on any element -->
22<nav data-active-section="pricing">
23 <a href="/" data-page="home">Home</a>
24 <a href="/pricing" data-page="pricing">Pricing</a>
25 <a href="/contact" data-page="contact">Contact</a>
26</nav>
27
28<input
29 type="text"
30 data-validation="email"
31 data-min-length="3"
32 data-max-length="254"
33 data-required="true"
34 placeholder="Email address"
35/>
36
37<article
38 data-id="post-42"
39 data-author="jane_doe"
40 data-published="2026-07-07"
41 data-category="html"
42 data-tags='["semantic","attributes","html5"]'
43>
44 <h2>Article Title</h2>
45 <p>Content...</p>
46</article>

warning

Data attributes must not be used for data that has a dedicated HTML attribute. For example, use class for CSS classes, id for unique identifiers, href for links, and src for images. Data attributes are for custom application data only — they should not duplicate existing semantics.
Accessing in JavaScript

JavaScript provides two ways to access data attributes: the dataset property (modern, convenient) and the getAttribute / setAttribute methods (compatible, explicit). The dataset property returns a DOMStringMap with all data attributes as camelCase keys.

The camelCase conversion follows a simple rule: each hyphen removes the hyphen and capitalizes the next letter. So data-user-id becomes dataset.userId, and data-config-theme-mode becomes dataset.configThemeMode.

HTML Attributedataset PropertygetAttribute
data-user-idelement.dataset.userIdelement.getAttribute('data-user-id')
data-configelement.dataset.configelement.getAttribute('data-config')
data-item-countelement.dataset.itemCountelement.getAttribute('data-item-count')
data-json-dataelement.dataset.jsonDataelement.getAttribute('data-json-data')
data-js.html
HTML
1<!-- HTML with data attributes -->
2<button
3 id="save-btn"
4 data-action="save"
5 data-document-id="doc_42"
6 data-version="3"
7 data-published="false"
8>
9 Save Document
10</button>
11
12<ul id="user-list">
13 <li data-user-id="1" data-role="admin">Alice</li>
14 <li data-user-id="2" data-role="editor">Bob</li>
15 <li data-user-id="3" data-role="viewer">Charlie</li>
16</ul>
17
18<script>
19 // Using the dataset property (camelCase conversion)
20 const btn = document.getElementById('save-btn');
21
22 console.log(btn.dataset.action); // "save"
23 console.log(btn.dataset.documentId); // "doc_42"
24 console.log(btn.dataset.version); // "3" (string, not number!)
25 console.log(btn.dataset.published); // "false" (string, not boolean!)
26
27 // dataset is read/write — updates reflect in the DOM
28 btn.dataset.version = "4";
29 btn.dataset.lastSaved = Date.now().toString();
30
31 // Using getAttribute / setAttribute (explicit, always works)
32 console.log(btn.getAttribute('data-action'));
33 btn.setAttribute('data-last-modified', new Date().toISOString());
34
35 // Parsing data attribute values
36 const version = parseInt(btn.dataset.version, 10);
37 const published = btn.dataset.published === 'true';
38
39 // Working with JSON data attributes
40 const listItems = document.querySelectorAll('[data-user-id]');
41 listItems.forEach(item => {
42 const userId = parseInt(item.dataset.userId, 10);
43 const role = item.dataset.role;
44 console.log('User ' + userId + ':', role);
45 });
46
47 // Accessing all data attributes at once
48 const allData = btn.dataset;
49 for (const key in allData) {
50 console.log('data-' + key + ':', allData[key]);
51 }
52
53 // Removing a data attribute
54 delete btn.dataset.tempData;
55 // or: btn.removeAttribute('data-temp-data');
56</script>

info

Values from dataset are always strings. To use numbers, booleans, or objects, you must parse them explicitly: parseInt(dataset.count, 10) for integers, dataset.active === 'true' for booleans, and JSON.parse(dataset.config) for objects. The getAttribute method also returns strings (or null if the attribute does not exist).

The dataset camelCase conversion is one of the most common points of confusion. Remember: the hyphenated HTML attribute name maps to a camelCase JavaScript property:

dataset-camelcase.js
JavaScript
1// data-my-name → dataset.myName
2// data-config-theme-mode → dataset.configThemeMode
3// data-api-endpoint-url → dataset.apiEndpointUrl
4// data- → dataset. (invalid — must have at least one character after data-)
5// data-s → dataset.s (single letter is fine)
6// data-42 → invalid HTML (numbers not allowed after data-)
7
8// Special case: data- is the prefix only
9// data-xml-data → dataset.xmlData
10
11// The reverse map: camelCase to HTML attribute
12function toDataAttribute(camelCase) {
13 return camelCase.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
14}
15
16console.log(toDataAttribute('userId')); // "user-id"
17console.log(toDataAttribute('apiBaseUrl')); // "api-base-url"

best practice

Prefer dataset for reading and writing data attributes — it is cleaner and performs well. Use getAttribute when you need to check for attribute existence (returns null vs undefined) or when working with older browsers. The dataset API is supported in all modern browsers and IE 11+.
Accessing in CSS

CSS provides two mechanisms for working with data attributes: attribute selectors to match elements, and the attr() function to display attribute values in pseudo-elements. The attr() function currently only works within content property of pseudo-elements, though CSS Values Level 5 proposes extending it to all CSS properties.

data-css.css
CSS
1/* Attribute selectors — target elements by data attribute */
2
3/* All elements with data-user-id */
4[data-user-id] {
5 border-left: 3px solid #00FF41;
6}
7
8/* Exact value match */
9[data-role="admin"] {
10 background: rgba(0, 255, 65, 0.1);
11}
12
13[data-status="active"] {
14 opacity: 1;
15}
16
17[data-status="inactive"] {
18 opacity: 0.4;
19}
20
21/* Value contains substring */
22[data-category*="html"] {
23 color: #00FF41;
24}
25
26/* Value starts with prefix */
27[data-attr^="data-"] {
28 font-family: monospace;
29}
30
31/* Value ends with suffix */
32[data-file-type$=".pdf"]::after {
33 content: " PDF";
34}
35
36/* Combined selectors */
37[data-role="admin"][data-active="true"] {
38 font-weight: bold;
39}
40
41/* Data attributes for state-driven styling */
42[data-theme="dark"] {
43 background: #0A0A0A;
44 color: #E0E0E0;
45}
46
47[data-theme="light"] {
48 background: #FFFFFF;
49 color: #1A1A1A;
50}
51
52/* Use attr() in pseudo-element content */
53[data-tooltip]:hover::after {
54 content: attr(data-tooltip);
55 position: absolute;
56 background: #111;
57 color: #00FF41;
58 padding: 4px 8px;
59 border-radius: 4px;
60 font-size: 11px;
61 white-space: nowrap;
62 z-index: 10;
63}
64
65[data-count]::before {
66 content: "(" attr(data-count) ")";
67 font-size: 10px;
68 color: #525252;
69 margin-right: 4px;
70}
71
72/* Style empty data attributes */
73[data-empty=""] {
74 opacity: 0.5;
75}
76
77/* Progress bar using data attributes */
78[data-progress] {
79 position: relative;
80 height: 8px;
81 background: #222;
82 border-radius: 4px;
83}
84
85[data-progress]::before {
86 content: "";
87 position: absolute;
88 height: 100%;
89 background: #00FF41;
90 border-radius: 4px;
91}
data-css-html.html
HTML
1<!-- Using data attributes for CSS-driven UI state -->
2<div class="card" data-theme="dark" data-status="active">
3 <h2>Card Title</h2>
4 <p>Content with data-driven styling.</p>
5</div>
6
7<!-- Tooltip via data attribute -->
8<button data-tooltip="Click to save your changes">
9 Save
10</button>
11
12<!-- Dynamic badges -->
13<span data-count="12">Notifications</span>
14
15<!-- Tab system driven by data attributes -->
16<div class="tabs">
17 <button data-tab="overview" data-active="true">Overview</button>
18 <button data-tab="details" data-active="false">Details</button>
19 <button data-tab="settings" data-active="false">Settings</button>
20</div>
Use Cases

Data attributes are versatile and appear in a wide range of patterns. Here are the most common use cases with practical examples.

Use CasePatternExample
State trackingBoolean or enumerationdata-active="true", data-state="loading"
IdentificationIDs or keysdata-user-id="42", data-post-slug="hello-world"
ConfigurationSettings for JS componentsdata-throttle="300", data-api-url="/api/v2"
ValidationForm field constraintsdata-min-length="3", data-pattern="^[a-z]+$"
AnalyticsTracking metadatadata-track="click", data-category="pricing"
TestingTest selectorsdata-testid="submit-button", data-qa="header"
InternationalizationTranslation keysdata-i18n="welcome.message"
Sorting / FilteringSort valuesdata-sort-value="100", data-filter="electronics"
data-use-cases.html
HTML
1<!-- State tracking: active states -->
2<nav>
3 <a href="/dashboard"
4 data-page="dashboard"
5 data-active="true">
6 Dashboard
7 </a>
8 <a href="/settings"
9 data-page="settings"
10 data-active="false">
11 Settings
12 </a>
13</nav>
14
15<!-- Configuration: JS behavior config -->
16<div
17 class="carousel"
18 data-autoplay="true"
19 data-interval="5000"
20 data-loop="true"
21 data-transition="slide"
22>
23 <div data-slide="1">Slide 1</div>
24 <div data-slide="2">Slide 2</div>
25 <div data-slide="3">Slide 3</div>
26</div>
27
28<!-- Analytics tracking -->
29<button
30 data-track="click"
31 data-category="signup"
32 data-label="pricing-page"
33 data-value="free-tier"
34>
35 Sign Up Free
36</button>
37
38<!-- Testing selectors -->
39<form data-testid="login-form">
40 <input
41 type="email"
42 data-testid="email-input"
43 placeholder="Email"
44 />
45 <button
46 type="submit"
47 data-testid="submit-btn"
48 >
49 Log In
50 </button>
51</form>
52
53<!-- Internationalization -->
54<h1 data-i18n="home.title">Welcome</h1>
55<p data-i18n="home.description">
56 This text can be replaced by a translation system.
57</p>
58
59<!-- Sorting data for JS-based sort -->
60<table>
61 <thead>
62 <tr>
63 <th data-sort="name">Name</th>
64 <th data-sort="price">Price</th>
65 </tr>
66 </thead>
67 <tbody>
68 <tr data-sort-name="Widget" data-sort-price="9.99">
69 <td>Widget</td>
70 <td>$9.99</td>
71 </tr>
72 <tr data-sort-name="Gadget" data-sort-price="24.99">
73 <td>Gadget</td>
74 <td>$24.99</td>
75 </tr>
76 </tbody>
77</table>

Live preview of a data-driven UI with state tracking and configuration:

preview
Dynamic Data Attributes

Data attributes can be updated dynamically via JavaScript to reflect application state. Changes to dataset properties are reflected in the DOM immediately, which means CSS selectors targeting those attributes react instantly — no additional JavaScript is needed to update styles.

This pattern is particularly powerful for theming, state management, and responsive behavior. You can change a single data attribute on a parent element and all descendant CSS selectors update automatically.

dynamic-data.html
HTML
1<!-- Theme toggling via data attribute -->
2<div id="app" data-theme="dark">
3 <header>
4 <button id="theme-toggle" data-action="toggle-theme">
5 Toggle Theme
6 </button>
7 </header>
8 <main>
9 <p>Current theme: <span id="theme-label">dark</span></p>
10 </main>
11</div>
12
13<style>
14 #app[data-theme="dark"] {
15 background: #0A0A0A;
16 color: #E0E0E0;
17 }
18 #app[data-theme="light"] {
19 background: #FFFFFF;
20 color: #1A1A1A;
21 }
22</style>
23
24<script>
25 const app = document.getElementById('app');
26 const label = document.getElementById('theme-label');
27
28 document.getElementById('theme-toggle').addEventListener('click', () => {
29 const current = app.dataset.theme;
30 const next = current === 'dark' ? 'light' : 'dark';
31 app.dataset.theme = next;
32 label.textContent = next;
33 });
34</script>
35
36<!-- Dynamic filtering with data attributes -->
37<style>
38 .item { display: block; }
39 .item[data-hidden="true"] { display: none; }
40</style>
41
42<div id="filter-controls">
43 <button data-filter="all">All</button>
44 <button data-filter="html">HTML</button>
45 <button data-filter="css">CSS</button>
46 <button data-filter="js">JavaScript</button>
47</div>
48
49<ul id="item-list">
50 <li class="item" data-category="html">HTML Elements</li>
51 <li class="item" data-category="css">CSS Grid</li>
52 <li class="item" data-category="js">JavaScript Events</li>
53 <li class="item" data-category="html">Semantic HTML</li>
54 <li class="item" data-category="css">Flexbox Guide</li>
55 <li class="item" data-category="js">DOM Manipulation</li>
56</ul>
57
58<script>
59 const filterButtons = document.querySelectorAll('[data-filter]');
60 const items = document.querySelectorAll('.item');
61
62 filterButtons.forEach(btn => {
63 btn.addEventListener('click', () => {
64 const filter = btn.dataset.filter;
65
66 items.forEach(item => {
67 if (filter === 'all' || item.dataset.category === filter) {
68 item.dataset.hidden = 'false';
69 } else {
70 item.dataset.hidden = 'true';
71 }
72 });
73 });
74 });
75</script>
Performance Considerations

Data attributes are stored in the DOM and are part of the element's attribute list. Reading and writing them is fast — on par with other DOM attribute operations. However, there are a few performance considerations to keep in mind.

OperationPerformanceRecommendation
Reading datasetFast (cached DOM property)Use for infrequent reads
Writing datasetTriggers attribute mutationBatch updates, avoid layout thrash
CSS selector [data-*]Fast (same as class selectors)Use for styling, not JS data storage in hot loops
Large JSON in datasetSlower (serialization cost)Store a reference key, keep data in JS memory
Many elements with data-*Memory proportional to attribute countAvoid thousands of data attributes; use JS Map instead
data-performance.js
JavaScript
1// Anti-pattern: storing large data in data attributes
2const items = document.querySelectorAll('[data-product]');
3items.forEach(item => {
4 // Each item has a large JSON string as data-product
5 const product = JSON.parse(item.dataset.product);
6 // This parses the JSON on every access
7});
8
9// Better: store a reference key, keep data in JS
10const productData = new Map();
11
12items.forEach(item => {
13 const id = item.dataset.productId;
14 const data = productData.get(id);
15 // Access productData map directly, no DOM parsing needed
16});
17
18// Anti-pattern: reading data attributes in a hot loop
19for (let i = 0; i < 1000; i++) {
20 const val = element.dataset.value;
21 // dataset access touches the DOM each iteration
22}
23
24// Better: cache the value outside the loop
25const cached = element.dataset.value;
26for (let i = 0; i < 1000; i++) {
27 // Use cached value, no DOM access in the loop
28 process(cached);
29}
30
31// Anti-pattern: using data-* for computed/derived state
32item.dataset.total = String(a + b); // String concatenation in DOM
33
34// Better: keep computed state in JS, use data-* for identification only
35const state = { total: a + b };
36item.dataset.itemId = String(id); // Just for identification
🔥

pro tip

Data attributes are ideal for identification and configuration metadata that needs to be accessible from both CSS and JavaScript. For complex application state, keep a JavaScript Map or WeakMap keyed by element references. This avoids serialization costs and keeps sensitive data out of the DOM.
Security

Data attributes are visible in the DOM and can be read by any JavaScript running on the page, including third-party scripts. Never store sensitive information like API keys, authentication tokens, session IDs, or personal data in data attributes.

Data TypeSafe in data-*?Why
User IDsYes (public)Safe as long as they are not secrets
API tokensNoVisible in DOM inspector, accessible to any script
Session tokensNoShould only be in HttpOnly cookies, never in DOM
Personal dataNoGDPR/PII compliance — data attributes are not encrypted
Public identifiersYesPost slugs, item SKUs, category names are fine
Configuration flagsYesFeature toggles, UI state flags are acceptable
data-security.html
HTML
1<!-- DANGEROUS: never store secrets in data attributes -->
2<button
3 data-api-key="sk-abc123..."
4 data-session-token="eyJhbGciOi..."
5 data-user-ssn="123-45-6789"
6>
7 ❌ Never do this
8</button>
9
10<!-- ACCEPTABLE: public identifiers and configuration -->
11<button
12 data-product-id="prod_42"
13 data-category="electronics"
14 data-currency="USD"
15 data-price="29.99"
16>
17 Add to Cart — $29.99
18</button>
19
20<!-- SAFE: boolean state flags for UI behavior -->
21<div
22 data-user-role="viewer"
23 data-feature-flags='["dark-mode","beta-search"]'
24>
25 <p>Profile content for a viewer role user.</p>
26</div>
27
28<!-- ALWAYS: sanitize data attribute values -->
29<!-- If values come from user input, escape them properly -->
30<div
31 data-description="Safe text — no HTML injection here"
32></div>
33
34<!-- NEVER: use data attributes to bypass CSP -->
35<script>
36 // Dangerous: reading token from data attribute
37 const token = document.querySelector('[data-token]').dataset.token;
38 // This token is visible to every browser extension and third-party script
39</script>

danger

Data attributes are not a security boundary. Any JavaScript on the page — including third-party scripts, browser extensions, and developer tools — can read dataset values. Treat data attributes as public information. For sensitive data, use server-side sessions, HttpOnly cookies, or the Credential Management API.
Best Practices

Data Attributes Checklist

Use data-* for custom application data only — never duplicate existing HTML attributes
Always use lowercase-plus-hyphens naming (data-user-id, not data-userId) in HTML
Access via dataset in JavaScript, falling back to getAttribute for compatibility checks
Parse values explicitly: parseInt for numbers, JSON.parse for objects, 'true'/'false' for booleans
Keep data attribute values small — store complex data in JavaScript Maps keyed by references
Never store sensitive data (API keys, tokens, PII) in data attributes — they are publicly visible
Use data-testid attributes for testing selectors to decouple tests from CSS classes
Use data attribute selectors in CSS for state-driven styling (data-active, data-theme)
Sanitize any user-provided values before putting them in data attributes to prevent XSS
Prefer semantic HTML over data attributes for conveying meaning — data-* is for application data only

best practice

Data attributes are a bridge between HTML, CSS, and JavaScript. Use them when you need to attach metadata that affects presentation (CSS selectors) or behavior (JS logic). For pure application state that never affects styling, use JavaScript closures, Maps, or reactive state management instead — they are faster and more secure.
$Blueprint — Engineering Documentation·Section ID: HTML-24·Revision: 1.0