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
| Weight | Treatment | Use |
|---|---|---|
| Primary | Solid --color-brand fill, --color-on-brand text, --radius-control | The one action the view exists for |
| Secondary | Transparent fill, --color-brand border and text, same radius and height | The alternative, or several actions of equal weight |
| Tertiary | Transparent, no border, --color-brand underlined text | Least 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:
| Requirement | Threshold |
|---|---|
| Button shape — fill or border — against its background | 3:1 |
| Button text against the button | 4.5:1 |
| Two buttons sharing a style, distinguished only by contrast | 3: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:
| State | Treatment |
|---|---|
| Hover | Layer --color-state-hover over the element |
| Press / active | Layer --color-state-press over the element |
| Focus | Visible 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.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.Remove the action and say why it is unavailable. "Private account — request to follow this person to see their work."
- 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-errorstyling 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-stackfrom their nearest common neighbour, and confirm. Inoperator, confirmation is typed (01-modes.md).
- Kind
- Spec
- Section
- Components and layout