data-attributes
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 |
button.secondary, button.small |
| 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.secondary |
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="m">...</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(--size-m);
}
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" |
| Element classes | button.secondary, button.ghost, button.small |
data-{category} |
data-topic="css", data-size="large" |
Configuration Attributes
| Pattern | Examples |
|---|---|
data-{property} |
data-columns="3", data-gap="m" |
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
More from profpowell/vanilla-breeze
api-client
Fetch API patterns with error handling, retry logic, and caching. Use when building API integrations, handling network failures, or implementing offline-first data fetching.
44xhtml-author
Write valid XHTML-strict HTML5 markup. Use when creating HTML files, editing markup, building web pages, or writing any HTML content. Ensures semantic structure and XHTML syntax.
10patterns
Reusable HTML page patterns and component blocks. Use when building common page types like FAQs, product listings, press releases, or other structured content.
8i18n
Write internationalization-friendly HTML pages. Use when creating multilingual content, setting lang attributes, handling RTL languages, or preparing content for translation.
7ci-cd
Configure GitHub Actions for automated testing, building, and deployment. Use when setting up CI/CD pipelines, automating releases, or managing deployment workflows.
7tdd
Test-Driven Development workflow. Auto-activates when creating new JS/TS files. Advisory mode suggests tests first; strict mode requires them.
2