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.

allowed_tools: Read, Write, Edit

$ Instalar

git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/data/data-attributes ~/.claude/skills/claude-skill-registry

// tip: Run this command in your terminal to install the skill


name: data-attributes description: 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. allowed-tools: Read, Write, Edit

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?

AspectClassesData Attributes
SemanticsPresentation-focusedMeaning-focused
State.is-active, .is-loadingdata-state="active"
Variants.btn-primary, .btn-largedata-variant="primary"
JS AccessclassList.toggle('active')dataset.state = "active"
ValidationCannot validate valuesCan define allowed values
ReadabilityClass soupSelf-documenting
CSS Selectors.btn.primary.largebutton[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

PatternExamples
data-statedata-state="loading", data-state="error"
data-{adjective}data-expanded, data-selected, data-active
Boolean presencedata-featured, data-disabled, data-required

Variant Attributes

PatternExamples
data-typedata-type="success", data-type="warning"
data-variantdata-variant="primary", data-variant="outline"
data-{category}data-topic="css", data-size="large"

Configuration Attributes

PatternExamples
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.json where possible
  • Use consistent naming patterns across the project
  • Prefer data-state for multi-state components
  • Use dataset API in JavaScript, not getAttribute
  • 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