Using data‑* Attributes and dataset in JavaScript

Hero image for Using data‑* Attributes and dataset in JavaScript. Image by Marija Zaric.
Hero image for 'Using data‑* Attributes and dataset in JavaScript.' Image by Marija Zaric.

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 frontend 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 standardsfriendly 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 builtin 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 dataid="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 datauserid becomes dataset.userId rather than dataset['userid'].


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 stylingoriented 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 readonly. 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, HTMLadjacent 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, standardsfriendly 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 builtin way to store custom element data.
  • JavaScript reads and writes them through the dataset property.
  • Dashed attribute names become camelCase property names in dataset.
  • Dataset values are strings and often need explicit conversion.
  • data* works best for small, elementlevel 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.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript