Live preview

Rendered from the tier-2 component tokens + foundation tokens per this spec — a spec-faithful rendering, not the production component (those live in each product repo). Variants and states come from the spec below.

primary
secondary

Anatomy

root
The button box: background, radius, padding, focus ring.
label
The text label — weight semibold, size driven by the size axis.
icon
Optional leading/trailing icon (e.g. the '→' on 'Continue'); inherits the label colour.

Token bindings

Each binding's value is resolved live from the built @beestera/tokens output — tier-2 component tokens (--button-*) collapse through the foundation tier.

Part Property Token Resolved Applies to
root border-radius --button-radius 0.875rem --button-radius → --radius-lg
root background --button-bg #ffd700 --button-bg → --color-brand variant:primary
root background --button-bg-hover #ffbb00 --button-bg-hover → --color-brand-strong variant:primary,state:hover
label color --button-fg #181818 --button-fg → --color-ink variant:primary
root background --button-secondary-bg #ffffff --button-secondary-bg → --color-surface variant:secondary
root background --button-secondary-bg-hover #fafafa --button-secondary-bg-hover → --color-surface-alt variant:secondary,state:hover
root border-color --button-secondary-border #e4e4e7 --button-secondary-border → --color-border variant:secondary
label color --button-fg #181818 --button-fg → --color-ink variant:secondary
root outline-color --button-focus-ring #181818 --button-focus-ring → --color-ink state:focus
root box-shadow --button-shadow 0px 1px 2px 0px #0000000d, inset 0px -2px 0px 0px #0000000d, inset 0px 0px 0px 1px #0000002e --button-shadow → --shadow-xs-skeuomorphic variant:primary
root background --button-disabled-bg #d4d4d8 --button-disabled-bg → --bg-quaternary → --color-neutral-300 state:disabled
label color --button-disabled-fg #71717a --button-disabled-fg → --text-quaternary → --color-neutral-500 state:disabled
label font-size --text-small 0.75rem size:sm
label font-size --text-body 1rem size:md

Variants

variant primarysecondary
Visual emphasis. primary = brand-yellow fill with ink label (the main CTA); secondary = surface fill with a hairline border (lower-emphasis actions).
size smmd
Control size — drives padding and label scale, both on the spacing grid. sm for dense contexts, md as the default.

State matrix

default
Resting appearance for the active variant.
hover
Background deepens (primary -> brand-strong; secondary -> surface-alt).
active
Pressed — matches the hover background.
focus
A 2px ink outline offset 2px from the box, visible on both brand and surface backgrounds.
disabled
Non-interactive: a bg-quaternary fill with a text-quaternary label, the brand gradient and the skeuomorphic shadow both dropped, and no pointer or keyboard activation. The Figma Primary/Disabled treatment drew the active brand gradient until the client corrected it on 2026-08-06 — which is why a sold-out camp card looked registerable. (Not yet in @repo/ui — see notes.)

Accessibility

Role
button (native <button>) or link (<a>) when it navigates
Contrast
Label vs background >= 4.5:1 (WCAG AA text). Ink on brand-yellow passes; ink on surface passes.
Focus order
Focusable in DOM order; focus ring MUST be visible (focus-visible), never suppressed.
Target size
Interactive height >= 44px (--spacing-touch-min) at md; sm reserved for dense, non-primary contexts.

Usage

Do

  • Use exactly one primary button per view for the main action.
  • Keep labels short and imperative ('Register', 'Find a Camp', 'Continue').
  • Render as <a> when the action navigates, <button> when it mutates.

Don't

  • Don't stack multiple primary buttons competing for attention.
  • Don't hardcode the radius/colour — bind to the --button-* tokens.
  • Don't give a Primary button a decorative rgba border — the unbound rgba(255,255,255,0.12) stroke was removed from all 50 Figma Primary variants on 2026-08-06. The lit top edge comes from --button-shadow's insets.
  • Don't remove the focus ring.

Implementations

swarm-os
packages/ui/src/Button.tsbuildButtonView Ships variant: primary|secondary, size: sm|md. Currently composes Tailwind utilities (bg-brand, rounded-lg, ...) onto the foundation tier; can migrate to var(--button-*) on its own cadence.

Figma source: App Buttons