Button ====== Spec: descriptive guidance, not a checkable rule. It carries no forbidden and instead pair, and no `jig check` detector applies to it directly. Section: Components and layout **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-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`).