ActionsStable

Button

Sourcecomponents/Button.astro

The real component — hover, press, tab to it.

Overview

3 variants × 6 states, on light, dark & image — the whole button system.

On light

default
hover
active
focus
disabled
loading
Primary
Secondary
Tertiary

On dark

default
hover
active
focus
disabled
loading
Primary
Secondary
Tertiary

On image

default
hover
active
focus
disabled
loading
Primary
Secondary
Tertiary

Variations

Ways to compose a button beyond the base variants — an icon, a form submit, or full-width. Each works with any variant.

Icon left — actions

Icon before the label — for actions the icon represents (Save, Download, Add). Slot the SVG first.

Icon right — direction

Icon after the label — to signal direction. A forward arrow on a “continue” or “start” CTA.

Submit (renders <button>)

With no href, Button renders a real <button>. Use type="submit" for the button inside a form.

Full width

Stretches to its container. Used for stacked CTAs on mobile and inside narrow cards (e.g. pricing tiers) where a button spans the full width.

Anatomy

The button’s internal spacing and text style.

  • Text — --fs-button · 17px · Medium 500
  • Padding — 14px vertical · --space-lg (24px) horizontal
  • Icon ↔ label gap — --space-sm (12px)
  • Radius — --radius (20px)

Grouping two buttons? See Button Group for the spacing.

Responsive

Default keeps its width; fullWidth stretches — the mobile CTA pattern. Switch device for real breakpoints; 1920 shows at 100% and scrolls.

Props

PropTypeDefaultNotes
variant 'primary' | 'secondary' | 'tertiary' 'primary' Primary = filled violet · Secondary = filled · Tertiary = outlined.
surface 'light' | 'dark' 'light' 'dark' = the on-image treatment (Secondary→white fill, Tertiary→white outline).
href string — If set (and not disabled), renders an <a>; otherwise a <button>.
type 'button' | 'submit' 'button' Ignored when it renders a link.
fullWidth boolean false Stretch to 100% — used for stacked mobile CTAs.
disabled boolean false Dims to 40% and blocks interaction; sets aria-disabled.
loading boolean false Shows a spinner (label hidden), blocks interaction, sets aria-busy.

Usage

Import the shared component and set props — never re-style a button by hand.

<Button variant="primary" href="/path">Label</Button>

<Button variant="secondary">Label</Button>

<Button variant="tertiary" surface="dark">Label</Button>

Usage rules

Two kinds — how to place buttons in a design, and how to build them in code.

Visual — placement

Do

Use one Primary per view — the single most important action. Pair it with a Secondary or Tertiary for the lesser action.

Switch to the surface="dark" treatment on imagery / dark sections so the button stays legible.

Don't

Place two Primaries side by side, or Primary next to Secondary as equals — it removes the hierarchy. Downgrade one.

Mix icon-left and icon-right in the same button, or add an icon that doesn’t clarify the action.

Code — implementation

Do

Import the shared Button; use variant + surface; slot an SVG for an icon.

Pass href for navigation (renders <a>) and type="submit" for forms (renders <button>).

Don't

Hand-roll a styled link. Writing background: var(--fg-accent) + border-radius? Import Button instead.

Override the size, radius or font — they come from tokens (--fs-button, --radius).