Button
Nine variants, each with a specific job. Six are solid color fills — primary (gold) for the committing action, secondary (silver-blue) for the alternative, tertiary (bronze) for third-priority actions, caution (warm amber) for review-then-proceed warnings, critical (red) for destructive, irreversible actions, affirm (green) for confirming positive outcomes. Three are chrome utilities — ghost (flat gold text for Sign in), bare (neutral utility icon), ambient (a floating circle on the overlay plane). Buttons sit on the page like chips of solid color — white type on a filled surface, no glass, no metal, anywhere.
These buttons don't shimmer. The hover shimmer is the proposed system's move. Official buttons answer your pointer with motion instead: hover lifts the button 1px and deepens its fill; press scales it down to 0.97. The feedback is the fun — quick, physical, and out of your way before you notice it.
Variants
Sizes
Two sizes — md is the default; sm for compact toolbars and inline density.
With icons
Filled variants require a leading icon by type. Optional trailing iconAfter. Ghost prohibits icons by type (text-style links don't take glyphs). Gap is a token (--space-2).
States
Hover lifts the button and deepens its fill; press scales it down; :focus-visible shows a 2px gold outline (keyboard-only, never on mouse click); disabled drops opacity to 0.45 and disables the pointer.
Collapsed
At collapsed width a filled button switches to its icon-only form — a circle holding just the glyph — but variant identity stays. A collapsed primary is still gold; a collapsed critical is still red. A circle, because there's no text left to carry. The collapsed prop is the explicit primitive; a future container-query trigger will fire automatically when horizontal space narrows. Ghost prohibits collapsing by type (text-style links have no icon to fall back to).
When collapsed, the button becomes icon-only — same accessible-name rule as ambient and bare: aria-label is required so screen readers still announce the action.
Paint-in / paint-out
Paint-in (disabled → enabled) keeps it simple here — the laser-engraver sweep belongs to the proposed system. An official button just fades up to full color over --dur-base — no sweep, no ceremony: it wakes up and gets to work. Paint-out (enabled → disabled) is just opacity fading to inert over --dur-quick — quietly out of the way; whatever caused the disable owns the attention. Buttons never use --dur-deliberate. Reduced-motion skips entirely.
When to use which
- primary
- One per surface. The committing action — Save, Send, Submit, Sign in (form). Solid gold draws the eye; use it sparingly.
- secondary
- The alternative to primary — Cancel, Reset, a non-committing alternative. Solid silver-blue. Can repeat per surface.
- tertiary
- Solid bronze. Third-priority actions in dense surfaces where primary and secondary are both already in use.
- caution
- Warm amber. Review-then-proceed warnings — the user should pause, but the action isn't destructive. Override, dismiss-with-consequences.
- critical
- Solid red. Destructive, irreversible actions — Delete, Purge, Revoke. Pair with a confirmation dialog.
- affirm
- Solid green. Confirms a positive outcome — Approve, Accept, Mark resolved.
- ghost
- Text-style action in a conventional spot — Sign in (chrome entry), Forgot?, secondary nav links. No outline because the position already carries the affordance.
- bare
- Inline icon control in a chrome bar — Site language, app-bar utility icons. No border or fill because the bar already contains it.
- ambient
- Floating circle on the overlay plane — Preferences (BL), Comms (BR), Portal language (TR). Flat fill with a soft shadow — a circle, because it floats free.
Accessibility
- Icon-only variants (ambient; bare when used without a label) must receive an aria-label — and the label should carry state when state matters (e.g., "Language: English", not just "Language").
- Focus is keyboard-only (:focus-visible) and renders a 2px gold outline. Never use mouse-focus styling — it competes with hover.
- Disabled buttons drop opacity and disable the pointer; the underlying element is still announced by screen readers as "dimmed" — never hide.
- The animation is governed by --ease-mechanical at --dur-quick; reduced-motion disables the transition.
Usage
import { Button } from '@halo-compliance/ui'
<Button variant="primary" icon={Send} onClick={save}>
Save changes
</Button><Button variant="ghost" onClick={signIn}>Sign in</Button><Button variant="bare" aria-label="Language: English">{LanguageGlyph}</Button>
<Button variant="ambient" aria-label="Preferences">{SlidersGlyph}</Button>Related
- /card — Card.Footer is where buttons mount inside a card.
- /comms, /preferences — the ambient variant powers the silver bottom corners.
- /app-bar#account — the ghost variant powers the Sign in CTA.
- /error-states — page/section ErrorState fallbacks use Button for their recovery affordances.