defuss-shadcn / Guides / data-attribute-api
Data Attribute API
In React or Solid, you configure components with props. In defuss-shadcn, data attributes are your props. Every component uses a single CSS class to declare what it is, and data-* attributes to declare which version - variant, size, orientation, and behavior.
On this page (8)
§Props → Data Attributes
If you came from a component framework, this mapping should feel familiar.
<Button variant="destructive" size="lg"> Delete</Button><button class="btn" data-variant="destructive" data-size="lg"> Delete</button>§How it works
Every component has exactly two layers in its markup: a class and its data attributes.
classIdentifies what the element is - .btn, .badge, .card, .dialog. One class per component. This is the base selector in CSS.data-variantSelects the visual style - default, outline, ghost, destructive, etc. Think of it as the component's "skin."data-sizeSelects the size - sm, lg, icon, etc. Controls height, padding, and font-size.data-*Additional axes like data-orientation, data-side, data-position - each controls one independent dimension. Compose freely.The CSS combines these with attribute selectors: .btn[data-variant="outline"]. No modifier classes, no BEM suffixes, no utility chains.
§Why data attributes instead of modifier classes?
Traditional systems like Bootstrap and BEM use modifier classes: .btn-primary, .btn--lg, .btn-outline-danger. Here's why we don't.
<button class="btn btn-destructive btn-lg"> Delete</button><style> /* Conflicting specificity */ .btn-primary { ... } .btn-lg { ... } .btn-primary.btn-lg { ... }</style><button class="btn" data-variant="destructive" data-size="lg"> Delete</button><style> /* Flat specificity - no combos needed */ .btn[data-variant="destructive"] { ... } .btn[data-size="lg"] { ... }</style>!important.Independent axesVariant and size combine freely without needing combinatorial class names like .btn-outline-lg.Uniform APIEvery component follows the same pattern. Learn it once, apply everywhere. Easy for AI to generate.Clean class attributeThe class attribute stays a single token - class="btn" - not a long chain of modifiers.§Styling attributes
These are set in your HTML and read by CSS. They control the visual appearance of components.
data-variant
The most common attribute. Controls the visual style of a component.
| Component | Values |
|---|---|
.btn | default · secondary · outline · ghost · destructive · link |
.badge | default · secondary · outline |
.tag | default · secondary · outline |
.alert | default · destructive |
.toggle | default · outline |
.toggle-group | default · outline |
.toast | default · destructive · success · warning · info |
.link | default · muted |
.tab-list | default · line |
.list | default · none · icon |
.dropdown-item | default · destructive |
data-size
Controls height, padding, and sometimes font-size.
| Component | Values |
|---|---|
.btn | xs · sm · default · lg · icon · icon-sm · icon-lg |
.input | sm · default · lg |
.select | sm · default · lg |
.dialog | sm · default · lg · xl · full |
.toggle | sm · default · lg |
.toggle-group | sm · default · lg |
.avatar | sm · default · lg |
.spinner | sm · default · lg |
.switch | sm · default |
Other styling attributes
Some components have additional axes beyond variant and size.
| Attribute | Component | Values | Purpose |
|---|---|---|---|
data-orientation | .btn-group · .sep · .toolbar | horizontal · vertical | Layout direction |
data-side | .sheet (dialog) | left · right · top · bottom | Slide-in direction |
data-position | .toast-container | top-left · top-right · top-center · bottom-left · bottom-right · bottom-center | Viewport placement |
data-ratio | .image | 1/1 · 4/3 · 16/9 · 21/9 | Aspect ratio |
data-layout | .descriptions | horizontal · vertical | Term/definition layout |
data-trend | .stat-trend | up · down | Trend indicator color |
data-spacing | .toggle-group | (boolean) | Adds gap between toggles |
§Behavioral attributes
These wire up JavaScript interactions. They follow the pattern data-{component}-trigger and data-{component}-close - connecting a button to the element it controls.
<!-- The trigger: its value is the ID of the target --><button class="btn" data-dialog-trigger="confirm"> Open Dialog</button><!-- The target: matched by ID --><dialog id="confirm" class="dialog"> <div class="dialog-content"> <p>Are you sure?</p> <!-- The close: placed inside, closes the nearest parent --> <button class="btn" data-dialog-close>Cancel</button> </div></dialog>| Attribute | Component | Purpose |
|---|---|---|
data-dialog-trigger | Dialog | Opens the <dialog> matching the attribute value |
data-dialog-close | Dialog | Closes the parent dialog |
data-alert-dialog-trigger | Alert Dialog | Opens an alert dialog by ID |
data-alert-dialog-close | Alert Dialog | Closes the parent alert dialog |
data-sheet-trigger | Sheet | Opens a sheet (drawer) by ID |
data-sheet-close | Sheet | Closes the parent sheet |
data-dropdown-trigger | Dropdown | Toggles a popover dropdown menu |
data-action | Number Input | increment / decrement buttons |
data-type | Accordion · Toggle Group | single or multiple selection mode |
data-collapsible | Accordion | Allows all panels to be closed |
§State attributes
These are set and removed by JavaScript at runtime to represent UI state. CSS reads them to apply state-specific styles. You don't write these in markup - the JS manages them.
| Attribute | Component | Purpose |
|---|---|---|
data-highlighted | Dropdown · Combobox | Marks the currently keyboard-highlighted item |
data-error | Avatar | Set when image fails to load, triggers fallback display |
data-placeholder | Combobox | Present when no selection has been made |
§ARIA attributes as styling hooks
Where the browser natively provides state APIs - like aria-selected, aria-expanded, or disabled - we style against those directly. No data attributes are needed because the browser already exposes the state.
/* Tab is selected - use ARIA, not a data attribute */.tab-trigger[aria-selected="true"] { color: var(--foreground); border-color: var(--primary);}/* Toggle is pressed - native ARIA attribute */.toggle[aria-pressed="true"] { background: var(--accent);}/* Button is disabled - native HTML attribute */.btn:disabled { opacity: 0.5; cursor: not-allowed;}Rule of thumb: if the browser has a native attribute for a state (disabled, aria-selected, aria-expanded, open), we style against that. Data attributes are only used when no native equivalent exists.
§The pattern at a glance
Every component follows this structure. Once you learn one, you know them all.
<element class="{component}" ← what it is data-variant="{variant}" ← visual style data-size="{size}" ← dimensions data-{axis}="{value}" ← other config data-{component}-trigger="{id}" ← JS wiring aria-*/disabled/open ← native state> content</element>Each component's component-skill.md lists the complete set of supported data attributes, variants, sizes, and ARIA requirements.
Comments, ideas or improvements? Edit this page's source on GitHub