Es gibt ein Muster, das ich in Codebases überall sehe: Entwickler müssen benutzerdefinierte Daten an ein DOM-Element anhängen, also kodieren sie es in Klassennamen (class="item item-42 status-active"), speichern es in versteckten Eingabefeldern oder fügen ein JavaScript-Objekt mit einer ID als Schlüssel hinzu. All das sind Workarounds. data-*-Attribute sind das richtige Werkzeug für diesen Zweck, und sie sind direkt im HTML Living Standard eingebaut.
Was data-*-Attribute sind
Die data-*-Attributspezifikation erlaubt es Ihnen, beliebige benutzerdefinierte Schlüssel-Wert-Daten an jedes HTML-Element anzuhängen. Der Name muss mit data- beginnen, gefolgt von mindestens einem Zeichen. Der Wert ist immer ein String.
<!-- data-* attributes: custom data right on the element -->
<button
data-action="delete"
data-item-id="284"
data-item-name="Summer Newsletter"
data-confirm="true"
>
Delete
</button>
<li
data-product-id="SKU-9921"
data-price="29.99"
data-in-stock="true"
data-category="electronics"
>
Wireless Keyboard
</li>Sie beeinflussen das Rendering überhaupt nicht. Sie fügen keine Klassen, Stile oder Layout-Verhalten hinzu. Sie sind reine Metadaten, lesbar von JavaScript und CSS, und werden von der Rendering-Engine des Browsers ignoriert.
Namenskonventionen
Datenattributnamen sind in HTML nicht case-sensitiv (die Spezifikation konvertiert sie zu Kleinbuchstaben), und sie werden in der dataset-API von JavaScript zu camelCase gemappt. Die Konvertierung erfolgt automatisch:
<!-- HTML attribute names use kebab-case -->
<div
data-user-id="42"
data-first-name="Alice"
data-account-type="premium"
data-last-login-at="2026-04-16T09:00:00Z"
>
</div>// JavaScript dataset uses camelCase (automatic conversion)
const el = document.querySelector('div');
el.dataset.userId; // "42" (from data-user-id)
el.dataset.firstName; // "Alice" (from data-first-name)
el.dataset.accountType; // "premium" (from data-account-type)
el.dataset.lastLoginAt; // "2026-04-16T09:00:00Z" (from data-last-login-at)Die Regel: Bindestriche in HTML werden in JavaScript zu camelCase. data-item-count → dataset.itemCount. Diese Konvertierung ist bidirektional — das Schreiben in dataset.itemCount aktualisiert data-item-count im DOM.
Lesen und Schreiben mit dataset
Die dataset-Eigenschaft ist eine DOMStringMap — sie verhält sich beim Lesen und Schreiben wie ein normales JavaScript-Objekt, und das DOM wird live aktualisiert:
const card = document.querySelector('.product-card');
// Reading
const productId = card.dataset.productId; // "SKU-9921"
const price = parseFloat(card.dataset.price); // 29.99 (remember: always a string, parse as needed)
const inStock = card.dataset.inStock === 'true'; // convert string to boolean
// Writing (updates the DOM attribute in real time)
card.dataset.price = '24.99'; // sets data-price="24.99"
card.dataset.inStock = 'false'; // sets data-in-stock="false"
card.dataset.badgeText = 'Sale'; // adds data-badge-text="Sale" (new attribute)
// Deleting
delete card.dataset.badgeText; // removes data-badge-text from the element
// Checking existence
if ('productId' in card.dataset) {
console.log('This is a product card');
}data-*-Werte sind Strings in HTML. Konvertieren Sie Zahlen immer mit parseInt() oder parseFloat(), und boolesche Werte durch Vergleich mit dem String 'true'. Sie ohne Konvertierung als ihren ursprünglichen Typ zu behandeln, ist eine häufige Fehlerquelle.Event-Delegation mit data-*-Attributen
Eine der praktischsten Anwendungen von Datenattributen: Event-Delegation. Anstatt einzelne Event-Listener an Dutzende von Buttons anzuhängen, fügen Sie einen Listener an ein übergeordnetes Element an und lesen die Aktion aus dem dataset des Elements:
<ul id="task-list">
<li data-task-id="101" data-status="pending">
Write article draft
<button data-action="complete" data-task-id="101">Complete</button>
<button data-action="delete" data-task-id="101">Delete</button>
</li>
<li data-task-id="102" data-status="pending">
Review pull request
<button data-action="complete" data-task-id="102">Complete</button>
<button data-action="delete" data-task-id="102">Delete</button>
</li>
</ul>const taskList = document.getElementById('task-list');
// One listener handles all buttons — even ones added dynamically later
taskList.addEventListener('click', (event) => {
const button = event.target.closest('button[data-action]');
if (!button) return; // click was somewhere else in the list
const action = button.dataset.action;
const taskId = button.dataset.taskId;
switch (action) {
case 'complete':
markTaskComplete(taskId);
break;
case 'delete':
deleteTask(taskId);
break;
}
});
function markTaskComplete(id) {
const item = taskList.querySelector(`li[data-task-id="${id}"]`);
item.dataset.status = 'complete'; // CSS can react to this change
console.log(`Task ${id} marked complete`);
}
function deleteTask(id) {
const item = taskList.querySelector(`li[data-task-id="${id}"]`);
item.remove();
console.log(`Task ${id} deleted`);
}Dieses Muster skaliert auf beliebig viele Elemente. event.target.closest() traversiert das DOM vom angeklickten Element nach oben, um den nächsten passenden Vorfahren zu finden — ein Klick auf den Button-Text (ein untergeordneter Textknoten) findet trotzdem den Button. Es ist sauberer als der Vergleich von event.target.tagName und zuverlässiger als die Prüfung exakter Elementübereinstimmungen.
data-* mit CSS attr() verwenden
CSS kann Datenattributwerte mit der attr()-Funktion in content-Deklarationen lesen. Dies ist nützlich für Tooltips, Badges und Beschriftungen, die aus Daten statt aus Markup kommen:
<!-- Tooltip text from data attribute -->
<span class="tooltip" data-tip="Saves automatically every 30 seconds">
Auto-save enabled
</span>
<!-- Status badge text from data attribute -->
<li class="task" data-status="in-progress">Review PR #412</li>
<li class="task" data-status="complete">Write unit tests</li>/* Tooltip using attr() in CSS */
.tooltip {
position: relative;
cursor: help;
text-decoration: underline dotted;
}
.tooltip::after {
content: attr(data-tip);
position: absolute;
bottom: 100%;
left: 0;
background: #1a1a2e;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.85rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.tooltip:hover::after,
.tooltip:focus::after {
opacity: 1;
}
/* Status badge via attr() */
.task::before {
content: '[' attr(data-status) '] ';
font-size: 0.75rem;
font-weight: bold;
text-transform: uppercase;
}
.task[data-status="complete"] { text-decoration: line-through; color: #6b7280; }
.task[data-status="in-progress"] { color: #d97706; }Beachten Sie, dass attr() derzeit nur in content-Eigenschaften funktioniert — Sie können es noch nicht für width, color oder andere CSS-Eigenschaften verwenden (obwohl die CSS Values Level 5-Spezifikation daran arbeitet, dies zu erweitern). Im Moment sind Attributselektoren ([data-status="complete"]) der praktische Weg, Stile basierend auf Datenattributwerten zu variieren.
Ein vollständiges interaktives Beispiel — Akkordeon mit data-*
Hier ist eine praxisnahe Akkordeon-Komponente, die vollständig durch Datenattribute für den Zustand gesteuert wird. Kein Klassen-Toggling für das Verhalten, nur Datenattribute — Klassen bleiben ausschließlich für das Styling:
<div id="faq-accordion" role="list">
<div class="accordion-item" role="listitem" data-open="false">
<button
class="accordion-trigger"
data-target="faq-1"
aria-expanded="false"
aria-controls="faq-1"
>
What is the difference between article and section?
</button>
<div id="faq-1" class="accordion-panel" data-panel hidden>
<p>An <code>article</code> is self-contained content that could stand alone.
A <code>section</code> is a thematic grouping within a larger context.</p>
</div>
</div>
<div class="accordion-item" role="listitem" data-open="false">
<button
class="accordion-trigger"
data-target="faq-2"
aria-expanded="false"
aria-controls="faq-2"
>
When should I use ARIA roles?
</button>
<div id="faq-2" class="accordion-panel" data-panel hidden>
<p>Only when no native HTML element conveys the same semantics.
Native elements should always be preferred.</p>
</div>
</div>
</div>const accordion = document.getElementById('faq-accordion');
accordion.addEventListener('click', (event) => {
const trigger = event.target.closest('.accordion-trigger');
if (!trigger) return;
const targetId = trigger.dataset.target;
const panel = document.getElementById(targetId);
const item = trigger.closest('.accordion-item');
const isOpen = item.dataset.open === 'true';
// Toggle this item
item.dataset.open = isOpen ? 'false' : 'true';
trigger.ariaExpanded = isOpen ? 'false' : 'true';
panel.hidden = isOpen;
});.accordion-trigger {
width: 100%;
text-align: left;
background: none;
border: none;
padding: 1rem;
font-size: 1rem;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
/* Arrow rotates based on data-open state */
.accordion-item[data-open="false"] .accordion-trigger::after {
content: '▼';
transition: transform 0.2s;
}
.accordion-item[data-open="true"] .accordion-trigger::after {
content: '▲';
}
.accordion-panel {
padding: 0 1rem 1rem;
}
.accordion-panel[hidden] {
display: none;
}Der Zustand liegt in data-open am Container. CSS liest dieses Attribut, um den Pfeilindikator zu drehen. JavaScript schaltet das Attribut um und verwaltet aria-expanded und hidden. Jede Schicht erledigt genau eine Aufgabe, und sie fügen sich sauber zusammen.
Hinweise zu Performance und Barrierefreiheit
- Performance.
data-*-Attribute sind Teil des DOM, und ihr Lesen ist sehr schnell — im Wesentlichen genauso wie das Lesen eines anderen Attributs. Für extrem häufig aufgerufene Code-Pfade (Tausende von Lesevorgängen pro Frame) ist eine JavaScript-Map mit Element-Referenz als Schlüssel schneller. Für typischen UI-Code istdatasetjedoch ausreichend. - data-* ist für Skripte, nicht für Semantik. Diese Attribute sind für Screenreader unsichtbar, es sei denn, Sie binden sie explizit an ARIA-Zustände. Verwenden Sie
data-roleoderdata-labelnicht, um Semantik zu vermitteln — nutzen Sie dafür geeignete HTML-Elemente und WAI-ARIA-Attribute. - Sicherheit. Legen Sie niemals sensible Daten (Tokens, Passwörter, personenbezogene Daten) in Datenattributen ab. Sie sind im Element-Inspektor des Browsers sichtbar und von jedem JavaScript auf der Seite lesbar, einschließlich Skripten von Drittanbietern.
- Abfragen. Sie können Elemente anhand von Datenattributen mit CSS-Attributselektoren auswählen:
document.querySelectorAll('[data-action="delete"]')— das funktioniert genau wie erwartet und ist sehr nützlich für Massenoperationen.
Werkzeuge für die HTML-Entwicklung
Bei der Arbeit mit HTML hält der HTML-Formatierer Ihr attributlastiges Markup lesbar, und der HTML-Validator erkennt Fehler wie doppelte IDs, die aria-controls-Verknüpfungen unterbrechen können. Für die Live-Bearbeitung und Vorschau von Komponentenmustern wie dem obigen Akkordeon ermöglicht der HTML-Editor, Änderungen in Echtzeit zu sehen — ohne Build-Schritt.
Fazit
Datenattribute sind der saubere, offizielle Weg, um benutzerdefinierte Metadaten an DOM-Elemente anzuhängen. Sie übertreffen Klassen-Hacks, versteckte Eingaben und externe JavaScript-Maps für die meisten Anwendungsfälle. Die dataset-API macht das Lesen und Schreiben einfach, die camelCase-Konvertierung ist automatisch, und sie fügen sich natürlich mit CSS-Attributselektoren und Event-Delegation-Mustern zusammen. Denken Sie nur daran: Datenattribute sind für skriptlesbare Metadaten, nicht für Barrierefreiheits-Semantik — dafür ist ARIA zuständig.