GitHub

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.

Stable .astro import EsaButton from '@esa/ecology/esa-button.astro';

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

PropTypeDefaultDescription
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).
  • active sets aria-pressed; iconOnly buttons require an aria-label.
  • loading keeps the label in the DOM (visually hidden) so width doesn't jump and the name persists.
  • warning fill 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.

TokenTierResolves 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