Data Attributes Skill
data-attributes
Using data-* attributes as the HTML/CSS/JS bridge for state, variants, and configuration. Use when managing element state, styling variants, or configuring behavior without JavaScript classes.
SKILL.md
Full skill instructions
Data Attributes Skill
This skill covers the use of data-* attributes as the preferred mechanism for state management, variant styling, and configuration in HTML-first development.
Philosophy
Data attributes replace classes for dynamic concerns. While semantic elements and custom elements handle structure, data-* attributes handle:
- State (
data-expanded,data-loading,data-valid) - Variants (
data-type,data-variant,data-topic) - Configuration (
data-columns,data-size,data-animation)
This creates a clean HTML/CSS/JS bridge where markup declares intent, CSS responds to it, and JavaScript (when needed) manipulates data attributes rather than class lists.
Why Data Attributes Over Classes?
| Aspect | Classes | Data Attributes |
|---|---|---|
| Semantics | Presentation-focused | Meaning-focused |
| State | .is-active, .is-loading | data-state="active" |
| Variants | .btn-primary, .btn-large | data-variant="primary" |
| JS Access | classList.toggle('active') | dataset.state = "active" |
| Validation | Cannot validate values | Can define allowed values |
| Readability | Class soup | Self-documenting |
| CSS Selectors | .btn.primary.large | button[data-variant="primary"] |
The Bridge Pattern
HTML: <element data-state="value"> → Declares state/config
CSS: [data-state="value"] { } → Styles based on state
JS: element.dataset.state = "new" → Updates state (if needed)
This separation means:
- HTML declares what the element is
- CSS defines how states look
- JavaScript only changes data attributes, not classes
Categories of Data Attributes
1. State Attributes
Track the current state of an element:
<!-- Expanded/collapsed -->
<nav data-expanded="false">...</nav>
<!-- Loading states -->
<button data-state="idle">Submit</button>
<button data-state="loading">Submitting...</button>
<button data-state="success">Sent!</button>
<button data-state="error">Failed</button>
<!-- Form validation -->
<form-field data-valid>...</form-field>
<form-field data-invalid>...</form-field>
<!-- Visibility -->
<modal-dialog data-open>...</modal-dialog>
2. Variant Attributes
Define visual or behavioral variants:
<!-- Type/category -->
<status-badge data-type="success">Active</status-badge>
<status-badge data-type="warning">Pending</status-badge>
<status-badge data-type="error">Failed</status-badge>
<!-- Topic/tag styling -->
<tag-topic data-topic="css">CSS</tag-topic>
<tag-topic data-topic="html">HTML</tag-topic>
<tag-topic data-topic="a11y">Accessibility</tag-topic>
<!-- Size variants -->
<user-avatar data-size="small" src="..." alt="..."/>
<user-avatar data-size="medium" src="..." alt="..."/>
<user-avatar data-size="large" src="..." alt="..."/>
3. Configuration Attributes
Configure component behavior or layout:
<!-- Grid configuration -->
<gallery-grid data-columns="3" data-gap="md">...</gallery-grid>
<!-- Animation settings -->
<carousel data-autoplay data-interval="5000">...</carousel>
<!-- Sort/filter settings -->
<data-table data-sortable data-sort-column="date">...</data-table>
<!-- Menu orientation -->
<nav-menu data-orientation="horizontal">...</nav-menu>
<nav-menu data-orientation="vertical">...</nav-menu>
CSS Selectors for Data Attributes
Attribute Presence (Boolean)
/* Element has data-featured (any value or no value) */
article[data-featured] {
border-left: 4px solid var(--primary-color);
}
/* Element has data-required */
form-field[data-required] label::after {
content: " *";
color: var(--error-color);
}
Exact Value Match
/* Exact match */
nav[data-expanded="true"] {
max-height: 500px;
}
nav[data-expanded="false"] {
max-height: 0;
overflow: hidden;
}
Multiple Values
/* Different states */
button[data-state="loading"] {
opacity: 0.6;
pointer-events: none;
}
button[data-state="success"] {
background-color: var(--success-color);
}
button[data-state="error"] {
background-color: var(--error-color);
}
Partial Match Selectors
/* Contains (anywhere in value) */
[data-tags*="featured"] { }
/* Starts with */
[data-category^="blog"] { }
/* Ends with */
[data-file$=".pdf"] { }
/* Space-separated word */
[data-tags~="important"] { }
Common Patterns
Expandable Navigation
<header>
<input type="checkbox" id="nav-toggle" hidden/>
<label for="nav-toggle" data-nav-trigger>Menu</label>
<nav data-mobile-nav>
<ul>...</ul>
</nav>
</header>
nav[data-mobile-nav] {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
#nav-toggle:checked ~ nav[data-mobile-nav] {
max-height: 500px;
}
Theme Variants
<article data-theme="light">...</article>
<article data-theme="dark">...</article>
article[data-theme="light"] {
--bg: white;
--text: #1f2937;
}
article[data-theme="dark"] {
--bg: #1f2937;
--text: white;
}
Loading Button
<button type="submit" data-state="idle">
<span data-label>Submit</span>
<span data-loader hidden>Loading...</span>
</button>
button[data-state="loading"] {
pointer-events: none;
opacity: 0.7;
& [data-label] { display: none; }
& [data-loader] { display: inline; }
}
Tag/Topic Styling
<tag-list>
<tag-topic data-topic="css">CSS</tag-topic>
<tag-topic data-topic="html">HTML</tag-topic>
<tag-topic data-topic="js">JavaScript</tag-topic>
<tag-topic data-topic="a11y">Accessibility</tag-topic>
</tag-list>
tag-topic {
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.875rem;
}
tag-topic[data-topic="css"] {
background: #dbeafe;
color: #1e40af;
}
tag-topic[data-topic="html"] {
background: #fee2e2;
color: #991b1b;
}
tag-topic[data-topic="js"] {
background: #fef3c7;
color: #92400e;
}
tag-topic[data-topic="a11y"] {
background: #d1fae5;
color: #065f46;
}
Grid Configuration
<gallery-grid data-columns="2">...</gallery-grid>
<gallery-grid data-columns="3">...</gallery-grid>
<gallery-grid data-columns="4">...</gallery-grid>
gallery-grid {
display: grid;
gap: var(--spacing-md);
}
gallery-grid[data-columns="2"] {
grid-template-columns: repeat(2, 1fr);
}
gallery-grid[data-columns="3"] {
grid-template-columns: repeat(3, 1fr);
}
gallery-grid[data-columns="4"] {
grid-template-columns: repeat(4, 1fr);
}
JavaScript Integration
When JavaScript is needed, use the dataset API:
Reading Data Attributes
const nav = document.querySelector('nav');
// Read attribute
const isExpanded = nav.dataset.expanded === 'true';
// Check presence (boolean attributes)
const isFeatured = nav.hasAttribute('data-featured');
Setting Data Attributes
// Set value
nav.dataset.expanded = 'true';
// Toggle boolean
if (nav.hasAttribute('data-featured')) {
nav.removeAttribute('data-featured');
} else {
nav.setAttribute('data-featured', '');
}
// State machine pattern
button.dataset.state = 'loading';
// After async operation
button.dataset.state = 'success';
Event Delegation
document.addEventListener('click', (event) => {
const trigger = event.target.closest('[data-action]');
if (!trigger) return;
const action = trigger.dataset.action;
// Handle action
});
Validation in elements.json
Define allowed data attributes and their values:
{
"status-badge": {
"flow": true,
"phrasing": true,
"permittedContent": ["@phrasing"],
"attributes": {
"data-type": {
"required": false,
"enum": ["success", "warning", "error", "info"]
}
}
},
"gallery-grid": {
"flow": true,
"permittedContent": ["@flow"],
"attributes": {
"data-columns": {
"required": false,
"enum": ["2", "3", "4"]
},
"data-gap": {
"required": false,
"enum": ["sm", "md", "lg"]
}
}
}
}
Naming Conventions
State Attributes
| Pattern | Examples |
|---|---|
data-state | data-state="loading", data-state="error" |
data-{adjective} | data-expanded, data-selected, data-active |
| Boolean presence | data-featured, data-disabled, data-required |
Variant Attributes
| Pattern | Examples |
|---|---|
data-type | data-type="success", data-type="warning" |
data-variant | data-variant="primary", data-variant="outline" |
data-{category} | data-topic="css", data-size="large" |
Configuration Attributes
| Pattern | Examples |
|---|---|
data-{property} | data-columns="3", data-gap="md" |
data-{setting} | data-autoplay, data-loop |
Checklist
When using data attributes:
- Use
data-*for state, not classes - Use boolean attributes (presence/absence) for true/false states
- Use value attributes for multiple states or variants
- Define allowed values in
elements.jsonwhere possible - Use consistent naming patterns across the project
- Prefer
data-statefor multi-state components - Use
datasetAPI in JavaScript, notgetAttribute - Document attribute purposes in component skills
Related Skills
- custom-elements - Define and use custom HTML elements
- javascript-author - Write vanilla JavaScript for Web Components with function...
- css-author - Modern CSS organization with native @import, @layer casca...
- progressive-enhancement - HTML-first development with CSS-only interactivity patterns
