Using data‑* Attributes and dataset in JavaScript

This Article is Over Eleven Years Old...
Things can and do move very quickly in tech, which means that tech-related articles go out of date almost as soon as they have been written and published. If you are looking for up-to-date technical advice or opinion, it is unlikely that you will find it on this page.
You may find that my recent articles are more relevant, and you are always welcome to drop me a line if you have a specific technical problem you are trying to solve.
One of the more awkward habits new front‑end developers pick up is hiding data in the wrong places. A class name ends up doing styling and behaviour and identity all at once. An id gets overloaded because it was available. A chunk of text is scraped back out of the DOM because there was nowhere else to put a small value.
That is usually a sign that data‑* attributes would have been the cleaner choice.
HTML data‑* attributes exist specifically so that you can attach custom data to elements in a standards‑friendly way. JavaScript then exposes those attributes through the dataset property, which gives you a simple interface for reading and writing them.
Once you understand when to use them, they are a very handy bridge between markup and behaviour.
What data‑* attributes are
Any HTML attribute beginning with data‑ is a custom data attribute.
For example:
<button class="remove-button" data-id="42" data-category="books"> Remove</button>These attributes do not have a built‑in browser meaning like href, src, or type. They are there for your code to use.
That makes them useful when an element needs a little extra information attached to it without turning that information into presentation or structural markup.
How dataset reads them in JavaScript
JavaScript exposes these custom attributes through the element's dataset property.
const button = document.querySelector<HTMLButtonElement>('.remove-button');console.log(button?.dataset.id);console.log(button?.dataset.category);If the HTML contains data‑id="42", then button.dataset.id gives you '42'.
That value is a string. This is worth remembering because dataset values behave much like other attribute values from the DOM: they come through as text unless you convert them yourself.
Dash Names Become Camelcase
This is one of the first little rules you need to know.
In HTML:
<div data-user-id="42" data-item-name="Notebook"></div>In JavaScript:
const card = document.querySelector<HTMLElement>('div');console.log(card?.dataset.userId);console.log(card?.dataset.itemName);The browser converts dashed attribute names into camelCase property names on dataset.
That is why data‑user‑id becomes dataset.userId rather than dataset['user‑id'].
Why This is Better than Abusing Classes
Developers sometimes use classes for everything because classes are already there and easy to select.
For example:
<button class="remove-button item-42">Remove</button>Then the code tries to pull meaning out of a styling‑oriented class name. That quickly gets messy.
data‑* attributes are usually cleaner because they separate concerns:
- classes describe styling or state
- ids identify elements structurally
data‑*attributes carry small pieces of custom data
That separation makes the markup more honest and the JavaScript easier to understand.
A Practical Event Delegation Example
One of the nicest uses for dataset is delegated event handling.
<ul class="results"> <li><button class="remove-button" data-id="41">Remove</button></li> <li><button class="remove-button" data-id="42">Remove</button></li></ul>const results = document.querySelector('.results');results?.addEventListener('click', (event) => { const target = event.target as HTMLElement; const button = target.closest<HTMLButtonElement>('.remove-button'); if (!button) { return; } console.log(button.dataset.id);});That works well because the button carries the exact value the handler needs, and the HTML stays readable.
You can write through dataset too
dataset is not read‑only. You can also update data attributes in JavaScript.
const panel = document.querySelector<HTMLElement>('.panel');if (panel) { panel.dataset.state = 'open';}That will update the corresponding attribute to:
<div class="panel" data-state="open"></div>This can be useful for simple status flags, metadata, or hooks that other code reads later.
The Values are Still Strings
Just like many other DOM attribute values, dataset values come through as strings.
That means this:
<button data-count="5"></button>gives you this in JavaScript:
const count = button?.dataset.count;where count is '5', not 5.
If you need a number or boolean, convert it explicitly.
const countValue = Number(button?.dataset.count ?? '0');const isActive = button?.dataset.active === 'true';That conversion step is small but important. Otherwise you end up with the same kind of type confusion that shows up with form input values.
dataset is good for small, HTML‑adjacent values
This is the limit worth understanding.
data‑* attributes are useful for small bits of information that belong naturally on the element:
- ids
- categories
- state labels
- analytics hooks
- display mode flags
They are less ideal for large structured data blobs or complex application state.
If you find yourself trying to cram a giant JSON payload into one data‑* attribute, that is often a sign the boundary is wrong. The HTML is no longer just carrying a small hint for behaviour; it is being treated like a database.
They Work Nicely with CSS Too, but That is Not Always the Goal
You can also select by data attribute in CSS or JavaScript:
[data-state='open'] { display: block;}document.querySelector('[data-state="open"]');That can be useful, but it is worth using carefully. Not every bit of application data needs to leak into selectors. Sometimes a normal class is still the better expression of a purely visual state.
Again, the real value is choosing the right tool for the right kind of meaning.
A Helpful Mental Rule
Ask yourself a simple question:
"Does this piece of information belong to this element as custom metadata?"
If the answer is yes, data‑* is often a good fit.
If the answer is "not really, I am only putting it there because I do not know where else to keep it", then it may be worth rethinking the structure.
Wrapping up
data‑* attributes and dataset give JavaScript a clean, standards‑friendly way to attach small pieces of custom data to HTML elements. They are particularly useful for event delegation, metadata, and lightweight state hints, and they help stop classes and ids from carrying meaning they were never meant to hold.
Key Takeaways
data‑*attributes are HTML's built‑in way to store custom element data.- JavaScript reads and writes them through the
datasetproperty. - Dashed attribute names become camelCase property names in
dataset. - Dataset values are strings and often need explicit conversion.
data‑*works best for small, element‑level metadata rather than large application state.
Used sensibly, dataset makes the connection between markup and behaviour much cleaner than overloading classes or scraping random text from the DOM.
Related Articles
DOM Traversal: closest() in Vanilla JavaScript and jQuery. DOM Traversal:

Understanding getStaticPaths in Next.js. Understanding
getStaticPathsin Next.js
Life as a Freelance Developer in Brighton. Life as a Freelance Developer in Brighton

Harnessing JavaScript's defineProperty(). Harnessing JavaScript's
defineProperty()
Manipulate Elements with CSS transform. Manipulate Elements with CSS
transform
Topological Sort: Solving the 'Course Schedule' Problem. Topological Sort: Solving the 'Course Schedule' Problem

Reverse an Array in JavaScript. Reverse an Array in JavaScript

Understanding Media Queries in CSS. Understanding Media Queries in CSS

Understanding CSS Positioning. Understanding CSS Positioning

React Portals Explained. React Portals Explained

Using classList in JavaScript: add(), remove(), toggle(), and contains(). Using
classListin JavaScript:add(),remove(),toggle(), andcontains()
What Does Front‑End Development Mean? What Does Front‑End Development Mean?