Data Attributes (data-*)
Custom data attributes let you attach your own private data to any HTML
element without inventing non-standard attributes or hijacking existing
ones. Any attribute named data-something is guaranteed valid
HTML and is exposed to JavaScript through the element's
dataset property.
Basic Syntax
data-attributes.html
<article class="product-card" data-id="4821" data-category="electronics" data-in-stock="true" > <h3>Wireless Mouse</h3> </article>
The prefix must be exactly
data-(lowercase).Everything after the prefix can be any lowercase name, using hyphens to separate words.
Values are always strings — there is no built-in type conversion for numbers or booleans.
Reading Data Attributes with dataset
In JavaScript, every data-* attribute on an element is
readable and writable through element.dataset, using a
camelCase version of the attribute name (hyphens are removed and the next
letter is capitalized).
reading-dataset.js
const card = document.querySelector('.product-card')
console.log(card.dataset.id) // "4821"
console.log(card.dataset.category) // "electronics"
console.log(card.dataset.inStock) // "true" (still a string!)
// Writing works the same way, and updates the attribute in the DOM
card.dataset.inStock = 'false'
// <article ... data-in-stock="false"> in the live DOM"4821" "electronics" "true"
data-in-stock in HTML becomes dataset.inStock in JavaScript, and assigning dataset.inStock = "false" updates the data-in-stock attribute back in the DOM. The mapping is always hyphen-case in HTML, camelCase in JavaScript.Common Use Cases
Storing a database or API identifier on a rendered element (
data-id="4821") so click handlers know which record to act on.Storing lightweight UI state flags (
data-open="true",data-selected="false") that CSS can also target via attribute selectors.Passing simple configuration values from server-rendered HTML to a small client-side script (
data-refresh-interval="30").Marking elements as JS "hooks" (
data-js="toggle-menu") separate from styling classes, so a redesign that changes CSS classes does not break behavior.
click-handler-example.html
<ul id="cart">
<li data-id="101" data-price="19.99">T-Shirt <button data-action="remove">Remove</button></li>
<li data-id="102" data-price="34.50">Mug <button data-action="remove">Remove</button></li>
</ul>
<script>
document.getElementById('cart').addEventListener('click', (event) => {
if (event.target.dataset.action === 'remove') {
const item = event.target.closest('li')
console.log('Removing item', item.dataset.id, 'priced at', item.dataset.price)
item.remove()
}
})
</script>Styling with Data Attributes
CSS can select on data attributes directly, which is a common way to drive visual state without toggling class names for every possible value.
data-attribute-css.html
<style>
.badge[data-status="active"] { background: #2e7d32; color: white; }
.badge[data-status="paused"] { background: #ed6c02; color: white; }
</style>
<span class="badge" data-status="active">Active</span>
<span class="badge" data-status="paused">Paused</span>Don't Invent Non-Standard Attributes
userid="4821" or active="true" directly on an element — these are invalid HTML, will fail markup validation, and may collide with a future standard attribute of the same name. Always prefix custom data with data-.Instead of | Use |
|---|---|
|
|
|
|
|
|
Values Are Always Strings
Because dataset values are strings, remember to convert them
explicitly when you need a number or boolean.
type-conversion.js
const el = document.querySelector('[data-quantity="3"]')
// Wrong: string concatenation, not addition
console.log(el.dataset.quantity + 1) // "31"
// Right: convert first
console.log(Number(el.dataset.quantity) + 1) // 4
// Booleans need an explicit check too
const isActive = el.dataset.active === 'true'JSON.parse(el.dataset.config). Keep this to small, non-sensitive payloads — data attributes are visible in the page source.Quick Reference
HTML attribute | JS dataset property |
|---|---|
|
|
|
|
|
|
|
|