data-* Toggle Styling
Style elements by their data-* attribute values using CSS — tabs, toggles, and state machines without JavaScript classes.
Quick implementation
/* Active tab */
.tab[data-state="active"] {
background: var(--accent-bg);
color: oklch(1 0 0);
}
/* Badge variants */
.badge[data-variant="success"] {
background: oklch(0.28 0.1 145);
color: oklch(0.82 0.16 145);
}
.badge[data-variant="warning"] {
background: oklch(0.3 0.1 75);
color: oklch(0.85 0.16 75);
}
.badge[data-variant="danger"] {
background: oklch(0.28 0.1 25);
color: oklch(0.82 0.18 25);
}
/* Any disabled element */
[data-disabled="true"] {
opacity: 0.45;
pointer-events: none;
cursor: not-allowed;
}Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior CSS engineer. Create a CSS system for styling components using data-* attributes instead of modifier classes.
Requirements:
1. Style tabs using [data-state="active"] — active tab gets accent background and white text.
2. Style badge variants using [data-variant="success|warning|danger|default"] with appropriate oklch() colors.
3. Style a disabled state using [data-disabled="true"] — reduced opacity, pointer-events: none.
4. Use oklch() for all colors. No hex, no rgba().
5. Use CSS custom properties for any repeated values.
Framework variants:
A) Vanilla HTML — apply data-* attributes directly to elements.
B) React — pass a `variant` prop that maps to data-variant on the root element.
Edge cases:
- Ensure [data-disabled] works on both <button> and <div> wrappers.
- Ensure active/inactive transitions use a short CSS transition (0.15s) for polish.
- Do not use :has() for browser compatibility — keep selectors simple.Why this matters in 2026
Using data-* attributes as style hooks decouples state management from CSS class manipulation, making component state readable directly in the HTML. Instead of toggling class names like .tab--active or .badge--danger, a single attribute reflects the semantic state. This approach aligns naturally with accessibility patterns (many ARIA states use a similar attribute model) and keeps your JavaScript focused on state logic rather than DOM class bookkeeping.
The logic
The CSS attribute selector [data-state="active"] matches any element where the data-state attribute equals "active", applying styles with the same specificity as a class selector. You can use exact matches ([attr="val"]), presence checks ([attr]), or substring matches ([attr*="val"]) depending on the use case. This makes it straightforward to build multi-variant systems — badge colors, size scales, or theme overrides — purely through attribute values set by your framework of choice.
Accessibility & performance
Attribute selectors have the same specificity as class selectors, so they integrate cleanly into existing CSS architectures without specificity wars. Where the attribute maps to a real ARIA state (like aria-selected or aria-disabled), prefer using those directly over custom data-* attributes to avoid duplicating state. Browser performance for attribute selectors is equivalent to class selectors in all modern engines.