Button
Triggers an action or event. A two-axis model — 8 semantic colors × 3 appearances (fill/outline/dashed) — plus a toggle, four sizes, and icon/loading affordances.
Preview
<EsaButton>Get started</EsaButton> Colors
Eight semantic colors. ai maps to the copper AI-context palette; ghost is neutral and transparent.
<EsaButton color="primary">Primary</EsaButton>
<EsaButton color="secondary">Secondary</EsaButton>
<EsaButton color="danger">Danger</EsaButton>
<EsaButton color="success">Success</EsaButton>
<EsaButton color="warning">Warning</EsaButton>
<EsaButton color="info">Info</EsaButton>
<EsaButton color="ai">AI</EsaButton>
<EsaButton color="ghost">Ghost</EsaButton> Appearances
The appearance axis is independent of color — any color renders as fill, outline, or dashed.
<EsaButton color="danger" appearance="fill">Fill</EsaButton>
<EsaButton color="danger" appearance="outline">Outline</EsaButton>
<EsaButton color="danger" appearance="dashed">Dashed</EsaButton> Active (toggle)
Set active for a pressed/selected look (and aria-pressed) — the basis for toggle buttons.
<EsaButton appearance="outline">Default</EsaButton>
<EsaButton appearance="outline" active>Active</EsaButton> Sizes
<EsaButton size="xs">Extra small</EsaButton>
<EsaButton size="sm">Small</EsaButton>
<EsaButton size="md">Medium</EsaButton>
<EsaButton size="lg">Large</EsaButton> States
<EsaButton icon="arrow-right">With icon</EsaButton>
<EsaButton loading>Loading</EsaButton>
<EsaButton disabled>Disabled</EsaButton>
<EsaButton iconOnly icon="plus" aria-label="Add" /> API
| Prop | Type | Default | Description |
|---|---|---|---|
color | 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'ai' | 'ghost' | 'primary' | Semantic color. `ai` uses the copper AI-context palette; `ghost` is neutral/transparent. |
appearance | 'fill' | 'outline' | 'dashed' | 'fill' | How the color renders — solid fill, bordered, or dashed border. |
active | boolean | false | Pressed/selected state (sets aria-pressed) — for toggle buttons. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'md' | Control size (shared form scale). |
icon | string | — | Lucide icon name, rendered left of the label. |
iconOnly | boolean | false | Square icon-only button with no text. |
loading | boolean | false | Shows a spinner; label is held in place but hidden. |
disabled | boolean | false | Disables interaction. |
type | 'button' | 'submit' | 'reset' | 'button' | Native button type. |
Accessibility
- Renders a native
<button>— keyboard and screen-reader behavior come for free. - Visible focus ring via
--focus-ring-*tokens (2px, offset). activesetsaria-pressed;iconOnlybuttons require anaria-label.loadingkeeps the label in the DOM (visually hidden) so width doesn't jump and the name persists.warningfill uses dark text for contrast on the amber background.
Theming surface
Every public token this component reads, extracted from its source at build time.
The Resolves to column walks each token's real lineage —
component → semantic → primitive → raw value — so you
can see exactly where re-pointing it lands. A spoke re-skins by overriding these in
its theme-<slug>.css; component internals are never edited. No
hook for what you need? File it with /request-lego.
| Token | Tier | Resolves to |
|---|---|---|
--button-on-warning | component | #202020 via --color-gray-12 |
--focus-ring-color | component | #65ba74 via --color-border-focus → --color-grass-8 |
--focus-ring-width | component | 2px |
--form-font-size-lg | component | clamp(0.875rem, 0.77rem + 0.52vw, 1.125rem) via --type-size-300 |
--form-font-size-md | component | clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --type-size-200 |
--form-font-size-sm | component | clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) via --type-size-100 |
--form-font-size-xs | component | clamp(0.5rem, 0.44rem + 0.3vw, 0.625rem) via --type-size-050 |
--form-height-lg | component | 48px |
--form-height-md | component | 40px |
--form-height-sm | component | 32px |
--form-height-xs | component | 28px |
--form-padding-x-lg | component | 1rem via --spacing-400 |
--form-padding-x-md | component | 0.75rem via --spacing-300 |
--form-padding-x-sm | component | 0.625rem via --spacing-250 |
--form-padding-x-xs | component | 0.5rem via --spacing-200 |
--form-radius-lg | component | 0.5rem via --radius-200 |
--form-radius-md | component | 0.5rem via --radius-200 |
--form-radius-sm | component | 0.25rem via --radius-100 |
--form-radius-xs | component | 0.25rem via --radius-100 |
--color-ai | semantic | #a18072 via --color-copper-9 |
--color-ai-hover | semantic | #957468 via --color-copper-10 |
--color-ai-strong | semantic | #7d5e54 via --color-copper-11 |
--color-border | semantic | #cecece via --color-gray-7 |
--color-border-strong | semantic | #bbbbbb via --color-gray-8 |
--color-danger | semantic | #e5484d via --color-red-9 |
--color-danger-hover | semantic | #dc3e42 via --color-red-10 |
--color-danger-strong | semantic | #ce2c31 via --color-red-11 |
--color-info | semantic | #0090ff via --color-status-info → --color-blue-9 |
--color-info-hover | semantic | #0588f0 via --color-blue-10 |
--color-info-strong | semantic | #0d74ce via --color-status-info-strong → --color-blue-11 |
--color-primary | semantic | #46a758 via --color-grass-9 |
--color-primary-hover | semantic | #3e9b4f via --color-grass-10 |
--color-primary-strong | semantic | #2a7e3b via --color-grass-11 |
--color-secondary | semantic | #65ba74 via --color-grass-8 |
--color-secondary-hover | semantic | #46a758 via --color-grass-9 |
--color-secondary-on-fill | semantic | #203c25 via --color-grass-12 |
--color-secondary-strong | semantic | #2a7e3b via --color-grass-11 |
--color-success | semantic | #bdee63 via --color-status-success → --color-lime-9 |
--color-success-hover | semantic | #b0e64c via --color-lime-10 |
--color-success-on-fill | semantic | #37401c via --color-lime-12 |
--color-success-strong | semantic | #5c7c2f via --color-status-success-strong → --color-lime-11 |
--color-surface | semantic | #fcfcfc via --color-gray-1 |
--color-surface-sunken | semantic | #f0f0f0 via --color-gray-3 |
--color-text-inverse | semantic | #fcfcfc via --color-gray-1 |
--color-text-primary | semantic | #202020 via --color-gray-12 |
--color-warning | semantic | #ffc53d via --color-status-warning → --color-yellow-9 |
--color-warning-hover | semantic | #ffba18 via --color-yellow-10 |
--color-warning-strong | semantic | #ab6400 via --color-status-warning-strong → --color-yellow-11 |
--focus-ring-offset | semantic | 2px |
--font-sans | semantic | 'DM Sans', sans-serif |
--transition-fast | semantic | 150ms ease |
--font-weight-medium | primitive | 500 |
--spacing-150 | primitive | 0.375rem |
--spacing-200 | primitive | 0.5rem |