Der er et mønster, jeg har set i kodebaser overalt: udviklere har brug for at vedhæfte brugerdefinerede data til et DOM-element, så de koder det ind i klassenavne (class="item item-42 status-active") eller gemmer det i skjulte inputfelter, eller tilføjer et JavaScript-objekt indekseret efter et ID. Alt dette er workarounds. data-*-attributter er det rigtige værktøj til jobbet, og de er bygget direkte ind i HTML Living Standard.
Hvad data-*-attributter er
Specifikationen for data-*-attributter lader dig vedhæfte enhver brugerdefineret nøgle-værdi-data til et hvilket som helst HTML-element. Navnet skal starte med data- efterfulgt af mindst ét tegn. Værdien er altid en streng.
<!-- 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>De påvirker overhovedet ikke renderingen. De tilføjer ingen klasser, stilarter eller layoutadfærd. De er rent metadata, der kan læses af JavaScript og CSS og ignoreres af browserens rendering-engine.
Navngivningskonventioner
Dataattributnavne er ufølsomme over for store og små bogstaver i HTML (specifikationen gør dem til små bogstaver), og de mappes til camelCase i JavaScripts dataset-API. Konverteringen sker automatisk:
<!-- 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)Reglen: bindestreger i HTML bliver til camelCase i JavaScript. data-item-count → dataset.itemCount. Denne konvertering er tovejs — at skrive til dataset.itemCount opdaterer data-item-count i DOM.
Læsning og skrivning med dataset
Egenskaben dataset er en DOMStringMap — den opfører sig som et almindeligt JavaScript-objekt ved læsning og skrivning, og DOM opdateres live:
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-*-værdier er strenge i HTML. Parse altid tal med parseInt() eller parseFloat(), og booleaner ved at sammenligne med strengen 'true'. At behandle dem som deres oprindelige type uden parsing er en hyppig kilde til fejl.Hændelsesdelegering med data-*-attributter
Et af de mest praktiske anvendelsesområder for dataattributter: hændelsesdelegering. I stedet for at vedhæfte individuelle hændelseslyttere til dusinvis af knapper, vedhæft én lytter til et overordnet element og læs handlingen fra elementets dataset:
<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`);
}Dette mønster skalerer til et vilkårligt antal elementer. event.target.closest() går op i DOM fra det klikkede element for at finde den nærmeste matchende forfader — så at klikke på knappteksten (en underordnet tekstnode) finder stadig knappen. Det er renere end at sammenligne event.target.tagName og mere pålideligt end at tjekke nøjagtige elementmatch.
Brug af data-* med CSS attr()
CSS kan læse dataattributværdier ved hjælp af funktionen attr() i content-deklarationer. Dette er nyttigt til tooltips, badges og etiketter, der kommer fra data snarere end markup:
<!-- 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; }Bemærk, at attr() i øjeblikket kun fungerer i content-egenskaber — du kan ikke bruge den til width, color eller andre CSS-egenskaber endnu (selvom CSS Values Level 5-specifikationen arbejder på at udvide dette). For nu er attributselektorer ([data-status="complete"]) den praktiske måde at variere stilarter baseret på dataattributværdier.
Et komplet interaktivt eksempel — harmonika med data-*
Her er en ægte harmonika-komponent drevet udelukkende af dataattributter til tilstand. Ingen klassetoggling til adfærd, kun dataattributter — klasser forbliver til styling alene:
<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;
}Tilstanden lever i data-open på containeren. CSS læser det attribut for at rotere pileindikatoren. JavaScript skifter attributtet og administrerer aria-expanded og hidden. Hvert lag gør præcist ét job, og de sammensættes rent.
Ydelses- og tilgængelighedsnoter
- Ydelse.
data-*-attributter er en del af DOM, og at læse dem er meget hurtigt — i bund og grund det samme som at læse et hvilket som helst andet attribut. For ekstremt varme kodestier (tusindvis af læsninger per frame) er et JavaScript Map indekseret efter elementreference hurtigere. Men til typisk UI-kode erdatasetfint. - Data-* er til scripts, ikke semantik. Disse attributter er usynlige for skærmlæsere, medmindre du eksplicit binder dem til ARIA-tilstande. Brug ikke
data-roleellerdata-labeltil at forsøge at formidle semantik — brug korrekte HTML-elementer og WAI-ARIA-attributter til det. - Sikkerhed. Læg aldrig følsomme data (tokens, adgangskoder, personoplysninger) i dataattributter. De er synlige i browserens elementinspektør og kan læses af ethvert JavaScript på siden, herunder tredjepartsskripts.
- Forespørgsler. Du kan vælge elementer efter dataattribut med CSS-attributselektorer:
document.querySelectorAll('[data-action="delete"]')— dette fungerer præcis som forventet og er meget nyttigt til masseoperationer.
Værktøjer til HTML-udvikling
Når du arbejder med HTML, holder HTML Formatter din attributtunge markup læsbar, og HTML Validator fanger fejl som duplikerede ID'er, der kan bryde aria-controls-associationer. Til live-redigering og forhåndsvisning af komponentmønstre som harmonika'en ovenfor, lader HTML Editor dig se ændringer i realtid uden et bygge-trin.
Afslutning
Dataattributter er den rene, officielle måde at vedhæfte brugerdefinerede metadata til DOM-elementer. De slår klassenavns-hacks, skjulte inputfelter og eksterne JavaScript-kort i de fleste anvendelsestilfælde. dataset-API'et gør det enkelt at læse og skrive dem, camelCase-konverteringen er automatisk, og de sammensættes naturligt med CSS-attributselektorer og hændelsesdelegeringsmønstre. Husk blot: dataattributter er til scriptlæsbar metadata, ikke til tilgængelighedssemantik — det er, hvad ARIA er til.