---
title: 'Data Attributes'
source: 'https://academia.sh/en/courses/web-fundamentals-and-html/data-attributes'
course: 'Web Fundamentals and HTML'
language: en
updated: '2026-08-17T18:09:32+00:00'
license: 'CC BY-SA 4.0'
---

# Data Attributes

Attaching data to a document without a name collision, the conversion between an attribute name and its program-side key, and the consequence of values always being strings.

The previous lessons completed the elements that give the document
meaning. What remains is a mechanism with no defined meaning but
useful: attributes that can get attached to any element,
produce no name collision, and can get read by the program.

The problem is this: an element may need information attached to it
that the language does not define — a sensor's ID, a record's status,
a section's refresh interval. This information is not visible in the
document but is needed by the behavior layer.

## Why a Separate Mechanism

The obvious ways of carrying such information are problematic.

Using an existing attribute outside its purpose — embedding data in
`title` or `class`, say — breaks that attribute's defined behavior:
`title` gets announced by a screen reader, `class` mixes into the
presentation layer's selectors.

Making up your own name — `sensor-id="4"`, say — does not produce a
valid document. As noted in the Markup Language Concept lesson, the
parser keeps an unknown attribute, so it works; but if the language
later adds an attribute named `sensor-id`, the document's meaning
changes silently.

The `data-` prefix answers both problems at once. The prefix is
reserved by the language: the language's own attributes never start
with this prefix, so a collision is impossible. And it has no defined
behavior: it does nothing until its value gets read.

```html
<article data-record-id="4"
         data-sensor-type="humidity"
         data-measurement-interval="600"
         data-verified="false">
  <h3>Humidity sensor readjusted</h3>
</article>
```

## Name Conversion

There is a defined conversion between the attribute name and the
program-side key. The attribute name gets written lowercase and
hyphenated; on the program side, the prefix drops and the hyphens get
removed with the following letter capitalized.

```js
// data.mjs — conversion between data-* attribute names and dataset keys
function attrToKey(writtenName) {
  const name = writtenName.toLowerCase(); // the parser lowercases attribute names
  if (!name.startsWith("data-")) return null;
  return name.slice(5).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}

function keyToAttr(key) {
  if (/-[a-z]/.test(key)) return null; // a hyphen before a lowercase letter is forbidden
  return "data-" + key.replace(/[A-Z]/g, (letter) => "-" + letter.toLowerCase());
}

const attributes = [
  "data-station",
  "data-measurement-interval",
  "data-last-calibration-date",
  "data-x2",
  "data-Sensor",
  "class",
];

console.log("attribute name                dataset key            round trip");
for (const name of attributes) {
  const key = attrToKey(name);
  const back = key === null ? "—" : keyToAttr(key);
  console.log(name.padEnd(30) + String(key ?? "—").padEnd(23) + String(back));
}

console.log("--- values are always strings ---");
const dataset = { station: "north-slope", measurementInterval: "600", active: "false" };
for (const [key, value] of Object.entries(dataset)) {
  console.log(key.padEnd(14), JSON.stringify(value), "-> typeof:", typeof value);
}
console.log("Number(measurementInterval) =", Number(dataset.measurementInterval));
console.log('Boolean("false")     =', Boolean(dataset.active));
```

```
attribute name                dataset key            round trip
data-station                  station                data-station
data-measurement-interval     measurementInterval    data-measurement-interval
data-last-calibration-date    lastCalibrationDate    data-last-calibration-date
data-x2                       x2                     data-x2
data-Sensor                   sensor                 data-sensor
class                         —                      —
--- values are always strings ---
station        "north-slope" -> typeof: string
measurementInterval "600" -> typeof: string
active         "false" -> typeof: string
Number(measurementInterval) = 600
Boolean("false")     = true
```

The fifth row shows a trap: the `data-Sensor` writing does not
produce the expected key, because, as in the Tags, Attributes, and
Entities lesson, the parser lowercases attribute names. An
attribute name does not use uppercase letters; multi-word names get
separated with a hyphen.

The fourth row shows that digits do not enter the conversion: a
hyphen only gets removed if a lowercase letter follows it.

## Values Are Strings

The output's second section shows data attributes' most often
overlooked property: values are always strings. Even if a number gets
written, it gets read as a string and has to get converted for
comparisons.

The last line is the consequence of this for boolean values. The
string `"false"` counts as true because it is not empty; the
`Boolean` conversion does not give the expected result. Carrying
boolean information takes one of two paths: comparing the value
explicitly, or following the boolean-attribute rule and counting
**presence** as the meaning — writing the attribute means true, never
writing it means false.

## When It Is Not Appropriate

A data attribute is not a storage space. It does not get used in
three cases.

**To carry content.** If a text that needs to be visible in the
document gets written into a data attribute, it exists in the source
text but not in the document: it cannot get selected, searched,
translated, or presented to a screen reader.

**For information the language already has a counterpart for.** The
information that an element is disabled, a section is hidden, a field
is required is defined in the language and declared with that
attribute. Writing this information into a data attribute conveys
nothing to the accessibility tree.

**For large data.** Attribute values get sent with the document on
every request, and parsing slows down as the document grows. Instead
of attaching a whole record, attaching its ID and requesting the data
separately gets preferred.

## Declaring State: Class or Data

There are two ways to declare an element's current state — selected,
loading, errored — and both are in use.

Adding a class name is common, but a class list is a set, carrying no
value: the information "state is loading" can only get told through
the presence of a class named `loading`, and keeping only one state
valid at a time is done by hand. If removing the old state gets
forgotten, two states appear at once.

A data attribute is a name-value pair; in the writing
`data-state="loading"`, a new value replaces the old one, and two
states existing at once is impossible. The presentation layer reads
this value with an attribute selector; selectors are the next
course's subject.

This distinction does not hold for states that need to get announced
to the user. The information that a field is errored, a button is
pressed, a section is expanded has to get conveyed to the
accessibility tree, via the language's own attributes and the ARIA
state attributes. A data attribute conveys nothing to that tree.

## Data Exposed to the Outside

Data attributes are a contract between the document's author and the
document's own scripts. Their names are free, their meanings are
specific to the document, and an outside program cannot make anything
of them.

If information needs to get exposed to the outside — to indexers, to
aggregators — this mechanism is not enough; a shared vocabulary is
needed. Structured data contracts embedded in the document do this,
resting on a defined set of concepts. The distinction is this:
`data-` is for your own program, structured data is for someone
else's.

## In the Station Document

```html
<section aria-labelledby="calibration-records" data-refresh-interval="600">
  <h2 id="calibration-records">Calibration Records</h2>

  <article data-record-id="4" data-sensor-type="humidity">
    <h3>Humidity sensor readjusted</h3>
    <p>A two percent deviation from the reference value was corrected.</p>
    <footer>
      <p>Record date: <time datetime="2024-03-12T07:30">March 12 morning</time></p>
    </footer>
  </article>
</section>
```

All three attached values are information that does not need to be
visible in the document and is useful to the behavior layer. The
record's date, though, is written to the `time` element, not a data
attribute: that information is both visible and has a counterpart in
the language.

## Summary

- The `data-` prefix is reserved by the language; attributes written
  with this prefix do not collide with the language's features and
  have no defined behavior.
- The attribute name gets written lowercase and hyphenated; the
  program-side key drops the prefix and capitalizes the letter after
  each hyphen.
- Values are always strings; they get converted for numeric
  comparisons, and in boolean information the string `"false"` counts
  as true.
- Content that needs to be visible in the document, information with
  a counterpart in the language, and large data do not get written to
  these attributes.

## Next Step

At this point the document is structured, sectioned, and carries
media; but it is one-directional. The reader receives information and
cannot give any. The next topic opens this direction: what a form
actually sends to the server, how that body gets encoded, and which
checks run before submission starts. The first lesson begins with
submission itself.
