Lesson 19 / 25
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.
Contents
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.
<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.
// 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
<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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.