Data Attributes: Storing Your Own Data in Markup

A legitimate way to attach custom information to an element, readable from CSS and JavaScript, without inventing invalid attributes.

Concept

Sometimes an element needs to carry information that no standard attribute expresses: a product identifier, a state, a configuration value a script will read. Inventing an attribute is invalid and risks colliding with a future standard one.

Data attributes are the sanctioned answer. Any attribute whose name begins with data- is valid, is ignored by the browser, and is available to your CSS and JavaScript.

Syntax

<article data-id="1043"
         data-category="lamps"
         data-in-stock="true"
         data-price="4999">
  <h3>Brass table lamp</h3>
</article>

Naming rules: after the data- prefix, at least one character, lower case, no capital letters, and no colon at the start. Use hyphens to separate words.

Reading them in JavaScript

const card = document.querySelector("article");

// dataset converts data-in-stock to inStock
card.dataset.id;        // "1043"
card.dataset.category;  // "lamps"
card.dataset.inStock;   // "true"

// writing back updates the attribute in the DOM
card.dataset.category = "lighting";
delete card.dataset.price;

// the older, longer way
card.getAttribute("data-id");

Two things catch people out. Hyphenated names become camel case in dataset, so data-in-stock is dataset.inStock. And every value is a string: dataset.inStock is the string "true", which is truthy even when it reads "false".

// wrong: any non empty string is truthy
if (card.dataset.inStock) { ... }

// right
if (card.dataset.inStock === "true") { ... }
const price = Number(card.dataset.price);

Using them in CSS

/* select by value */
[data-status="pending"] { border-left: 3px solid #f59e0b; }
[data-status="paid"]    { border-left: 3px solid #16a34a; }

/* select by presence */
[data-featured] { box-shadow: 0 0 0 2px #1e3a8a; }

/* substring matching */
[data-category^="light"] { }   /* starts with */
[data-tags~="sale"]      { }   /* one of a space separated list */

/* print the value */
.badge::after {
  content: attr(data-count);
}

Using a data attribute for state rather than toggling classes is often cleaner, because one attribute can hold many mutually exclusive values where classes would need adding and removing individually.

// one line, and the previous state is replaced
row.dataset.status = "paid";

Example: a filterable list

<div class="filters">
  <button type="button" data-filter="all">All</button>
  <button type="button" data-filter="lamps">Lamps</button>
  <button type="button" data-filter="vessels">Vessels</button>
</div>

<ul class="products">
  <li data-category="lamps"    data-price="4999">Brass table lamp</li>
  <li data-category="vessels"  data-price="2400">Copper jug</li>
  <li data-category="lamps"    data-price="7250">Hanging lantern</li>
</ul>
document.querySelector(".filters").addEventListener("click", (event) => {
  const filter = event.target.dataset.filter;
  if (!filter) return;

  document.querySelectorAll(".products li").forEach((item) => {
    const show = filter === "all" || item.dataset.category === filter;
    item.hidden = !show;
  });
});

Example: configuring a component from markup

<div class="chart"
     data-endpoint="/api/ridership"
     data-type="line"
     data-refresh="60"></div>
document.querySelectorAll(".chart").forEach((el) => {
  renderChart(el, {
    endpoint: el.dataset.endpoint,
    type: el.dataset.type,
    refreshSeconds: Number(el.dataset.refresh),
  });
});

The configuration lives beside the element it configures, which is easier to follow than a separate lookup table keyed by id.

What not to put in one

  • Anything sensitive. Data attributes are in the page source and fully editable by the reader. A price, a role, a user id or a discount that the server later trusts is a real vulnerability.
  • Content. If a reader needs to see it, it belongs in the text, not in an attribute. Text in an attribute is not selectable, not searchable, not translated and often not announced.
  • Accessibility information. That is what ARIA attributes are for; assistive technology does not read data-*.
  • Large blobs. A serialised JSON object in an attribute is a maintenance problem. Fetch the data instead.

Important rules

  • Names are lower case; hyphenated names become camel case in dataset.
  • Values are always strings.
  • Any element may carry any number of them.
  • They are valid HTML and are ignored by the browser.
  • Assistive technology does not expose them.
  • They are visible and editable in DevTools.

Common mistakes

  • Treating "false" as falsy.
  • Forgetting to convert a numeric value with Number().
  • Writing data-userId and being surprised that it becomes data-userid.
  • Storing content that should be visible text.
  • Storing a price and trusting it on the server.
  • Using them for accessibility instead of ARIA.
  • Inventing a bare attribute such as userid="12" instead of prefixing it.

Best practices

  • Prefix custom attributes with data-, always.
  • Use them for state a stylesheet needs to react to.
  • Convert types explicitly on read.
  • Keep them short and store identifiers rather than values.
  • Prefer a data attribute to a class when a value is one of several mutually exclusive states.
  • Never trust them on the server.

Practice

  1. Build a list of items with data-category and filter it with a single click handler.
  2. Style three states of a row using one data-status attribute and CSS attribute selectors.
  3. Read a numeric data attribute, add ten to it, and explain what happens if you forget to convert it.
  4. Print a count into a badge using content: attr(...).

Useful resources

Hand picked references for this topic
Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All HTML notes →
HTML

What HTML5 Actually Means

HTML5 is not a version you upgrade to. It is the name for a shift from a frozen specification to a living standard, and for the elements and APIs that...

Read more
HTML

The canvas Element

A blank bitmap you draw on with JavaScript. Powerful for graphics and games, and invisible to every reader who cannot see the screen.

Read more
HTML

SVG in HTML

Vector graphics that scale perfectly, weigh almost nothing and can be styled with CSS. Learn inline SVG, SVG as an image, and how to make one accessib...

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.