Skip to content
GitHub
Sections

Reference · Components and layout

Button

This is a spec: descriptive guidance, not a checkable rule. It carries no forbidden and instead pair, and no jig check detector applies to it directly.

The spec

Anatomy: [icon] label [icon] in a control of height --size-control, with a hit area of at least --size-touch-target (48px).

Three weights

WeightTreatmentUse
PrimarySolid --color-brand fill, --color-on-brand text, --radius-controlThe one action the view exists for
SecondaryTransparent fill, --color-brand border and text, same radius and heightThe alternative, or several actions of equal weight
TertiaryTransparent, no border, --color-brand underlined textLeast important actions, repeated actions, destructive actions

The tertiary underline is not optional. Without it, colour is the only thing distinguishing the button from plain text, which fails every colour-blind user. Proximity to other buttons may rescue it sometimes; that is not a control you can rely on.

The accessibility floors

Four numbers, and most button designs in the wild fail at least one:

RequirementThreshold
Button shape — fill or border — against its background3:1
Button text against the button4.5:1
Two buttons sharing a style, distinguished only by contrast3:1 between them
Hit area--size-touch-target — 48×48px floor

A secondary button's fill or border is not decorative. It is the only thing identifying the element as a button, so it carries the 3:1 non-text requirement (02-tokens.md). Strip it and you have coloured text.

Rules

  • Hierarchy must not depend on colour. Two buttons differing only in hue are identical to a colour-blind user, and if their contrast against each other is under 3:1 they are identical to a low-vision user too. Vary fill, border and underline — structure, not just colour.
  • One primary per view. Where several actions repeat down a list, they are all secondary or all tertiary; a column of primaries says every row is the most important thing on screen.
  • Equal importance means equal prominence. "Report" and "Don't report" are a genuine choice, so both are secondary. Making one primary applies a thumb to the scale.
  • Never a light grey secondary. It reads as disabled, and its text and border rarely clear their floors.
  • Never a second solid fill in another colour beside the primary. Two solid buttons compete, and the hierarchy collapses.
  • One shape for all weights. If the primary is a rounded rectangle, so is the secondary. A pill beside a rectangle implies a difference in function that does not exist.
  • Label is verb + noun: "Save post", "Delete invoice", "Add domain". Never "OK", "Submit", "Yes". Buttons are read out of context by screen reader users and by anyone scanning, so the label must work alone.
  • 16px minimum between adjacent buttons, so nobody hits the wrong one.
  • Width does not change between states. A loading button keeps its width, swaps the label for an indicator, and sets aria-busy.

Order and alignment

Start-aligned, ordered most to least important. The eye returns to the left edge moving down a screen; a right-parked primary can be missed entirely on a wide display or by anyone using a screen magnifier; and putting the most-used action first cuts the distance most people travel.

  • Mobile: stack top to bottom in the same order, full width, so either hand reaches them.
  • Dialogs: start-aligned, for consistency with every form in the product. Right alignment is defensible — it is the Mac convention and reads as forward momentum — but pick one and hold it everywhere.
  • Multi-step forms: primary start-aligned at the bottom; "Back" as a tertiary button at the top left, not beside the primary. A prominent Back next to Next gets clicked by mistake and the entered data is gone.
  • Exception: a single-field form — search, email capture — may attach the button to the end of the field. It saves space and reinforces that the two belong together.

Icon and text pairs

Match the icon's weight and size to the text it sits with. Where they cannot be matched — the icon set is heavier or larger than the type — bring the icon's contrast down instead: --color-stroke-strong for the icon against --color-text-weak for the label. The pair should read as one unit, with neither shouting over the other.

States

All required (E-28): default, hover, focus-visible, active, disabled, loading.

Transparent layers are the default treatment — no new tokens, and they work on every surface in both modes:

StateTreatment
HoverLayer --color-state-hover over the element
Press / activeLayer --color-state-press over the element
FocusVisible outline on --color-focus, never a fill change alone
Disabled--opacity-disabled — but see below

Alternatives where a layer is not enough: change the fill from the palette, change elevation (a card lifting on hover), toggle an underline (remove it from a link that has one, add it to a nav item that does not), or move the element a few pixels. Keep motion short and honour prefers-reduced-motion (G-43). Press usually matches default, since it only needs to differ from hover.

Instead of disabling

Disabled buttons give no feedback on press, often fail contrast, and are skipped by keyboard focus — so the user cannot even reach the thing to find out why it is dead. Three better options, in order:

  1. 1.
    Enable and validate on submit. Let them press it; show what is missing. A person who skipped a field learns that immediately instead of hunting for the reason the button will not work.
  2. 2.
    Remove the action and say why it is unavailable. "Private account — request to follow this person to see their work."
  3. 3.
    Keep the button, add a lock icon. Full contrast, discoverable, obviously gated. Works well for paid features, provided you say how to unlock them.

If you must disable: put a message beside the button explaining what is needed, or a tooltip on it, and keep it keyboard-focusable so assistive technology can reach the explanation.

Destructive actions

Friction scales with severity, and the first lever is prominence.

  • At rest, a destructive action is tertiary. Less prominent, further from the primary action, or disclosed behind something.
  • Do not colour it red at rest. Red makes it *more* prominent — the opposite of what friction means. --color-text-error styling belongs on the confirming button inside a confirmation step, where the user has already chosen and needs to understand the weight of it.
  • Destructive actions sit at least --spacing-stack from their nearest common neighbour, and confirm. In operator, confirmation is typed (01-modes.md).
Kind
Spec
Section
Components and layout

Read as plain text