Buttons

Buttons trigger immediate actions; navigation belongs to Link, though a link may wear the button classes — as a link, Space does not activate and the disabled and busy states do not apply. Choose intent first, then size.

Sizes

The size names remain Small, Medium, and Large; Medium is the modifier-free default. Buttons scale on both axes: text edges use space.2 / space.3 / space.4, height and owned icon size move with the same size, and the content gap stays at space.2.

ExampleSizeModifierHeightUsage
Small--smsize.interactive.smDense surfaces; button-sm type with space.2 inline padding
Medium · defaultds-buttonsize.interactive.mdForms and most surfaces; button-md type with space.3 inline padding — the control inset
Large--lgsize.interactive.lgHigh-emphasis flows; button-lg type with space.4 inline padding

Types

Type encodes intent, never decoration — choose by what the action means.

ExampleTypeModifierRolesUsage
Primary--primaryaction.primary.*The view's main action — one per view
Secondary--secondaryinherit · currentColorSupporting actions on any paired surface; the same-hue outline keeps them quiet
Ghost--ghostcontrol.*Lowest emphasis: toolbars, dense UI, repeated row actions
Danger--dangeraction.danger.*Destructive actions; confirm before irreversible ones
Buttons · Types × sizes

Every type at every size, side by side — intent first, then size.

Icon-only

Icon-only buttons require an accessible name that states the action, not the icon. The icon tracks the control size — sm with icon.sm, md with icon.md, lg with icon.lg.

ExampleSizeModifiersIconUsage
Small--icon--smicon.smDense toolbars, table row actions
Medium--iconicon.mdThe default icon action
Large--icon--lgicon.lgHigh-emphasis, touch targets
Small--ghost--icon--smicon.smCanonical standalone close or dismiss action for Sheet, Banner, Toast, and other surfaces
Buttons · Icon-only shapes

Shape is orthogonal to type: --rounded turns any icon-only button into a circle — square and circle across primary, secondary, and ghost.

Prefix and suffix

Pair icons with labels when the action benefits from quick scanning — the label leads, the icon supports. Labeled buttons pair icon.sm at sm and md; large steps up to icon.md. Text edges scale through space.2 / space.3 / space.4; an occupied icon edge is one step tighter at space.1 / space.2 / space.3.

ExampleIconModifiersPaddingUsage
Leading, small--sm--leading-iconspace.1space.2Compact button geometry scales on both edges
Leading--leading-iconspace.2space.3Icon reinforces the verb
Leading, large--lg--leading-iconspace.3space.4Type, icon, and inset step up together
Trailing, small--sm--trailing-iconspace.2space.1Compact button geometry scales on both edges
Trailing--trailing-iconspace.3space.2Direction or consequence follows the label
Trailing, large--lg--trailing-iconspace.4space.3Type, icon, and inset step up together
Both, small--sm--leading-icon--trailing-iconspace.1space.1Both occupied edges use the small optical inset
Both--leading-icon--trailing-iconspace.2space.2Rare — paired directions
Both, large--lg--leading-icon--trailing-iconspace.3space.3Both occupied edges use the large optical inset

Loading

Set aria-busy="true" instead of swapping in a spinner — the button stays focusable and announces the busy state.

Buttons · Loading

The spinner joins as a prefix and replaces any icons; the label stays visible and in the accessibility tree.

States

Every type answers every state in its own grammar: in the monochrome base, primary and danger drop their fills to text-grade outlines on hover and return to filled active states; secondary strengthens its outline, and ghost changes ink plus plate presence. Real DOM drivers — :hover, :active, :focus-visible, disabled, aria-busy — are forced here with demo classes and behave identically.

TypeRestHoverActiveFocusDisabledLoading
--primary
--secondary
--ghost
--danger

Usage notes

Button choices encode intent, not decoration.

VerdictRule
DoLabel verb + noun in sentence case (Deploy project, Invite member) — never bare Submit, OK, or Confirm.
DoKeep one primary per view; supporting actions are secondary or ghost.
DoReserve danger for destructive actions and pair irreversible ones 1:1 with a confirmation and an outcome toast (Delete project → Project deleted).
DoSet loading via aria-busy instead of swapping in a spinner — the button stays focusable and announces the busy state.
DoGive icon-only buttons an accessible name stating action and target (Copy deployment URL), not the icon (Copy).
DoAlways set an explicit type attribute — a bare <button> inside a form silently submits.
Don’tDisable as validation feedback — disable only when the action is impossible right now, and explain why nearby.
Don’tSet aria-label on a button with visible text — it creates a screen-reader mismatch.
Don’tAdd visual weight when disabling — disabled flattens color but keeps the variant's weight ladder: fill, outline, bare text.
Don’tOverride colors per instance — rebranding happens at the ramp level, never on one button.