Button
A native <button> with a closed set of variants and sizes. No JavaScript. Switch the brand: nothing in the component changes, only the semantic tokens behind it.
Variant × size
Defaults: data-variant="primary", data-size="md". Both also apply when the attribute is missing.
primarysecondaryghostStates
Hover, active and focus-visible come from :hover, :active and :focus-visible; disabled from the native disabled attribute; loading from aria-busy="true". Loading keeps focus, disabled cannot take it.
| Variant | default | hover | focus-visible | disabled | loading |
|---|---|---|---|---|---|
primary | |||||
secondary | |||||
ghost | |||||
| Point at it | Tab to it | disabled | aria-busy="true" |
Loading keeps focus
Activate the button with Enter or Space. It switches to aria-busy="true" and keeps focus; the click handler ignores activations while busy. The result is announced in a live region, not by the button.
Reduced motion
Colour changes between states use motion.duration.state-change and stay under reduced motion: they are feedback, not movement. The loading arc turns once per motion.duration.emphasis; with prefers-reduced-motion: reduce it stands still and the busy state stays visible (ADR 0003).
Usage
<link rel="stylesheet" href="@iahola/troupe-tokens/troupe.css">
<link rel="stylesheet" href="@iahola/troupe-components/button/styles.css">
<button type="button" class="troupe-button">Save</button>
<button type="button" class="troupe-button" data-variant="secondary" data-size="sm">Cancel</button>
<button type="button" class="troupe-button" aria-busy="true">Save</button>Full specification: packages/components/button/README.md.