Button
A rounded square you tap. One filled primary per screen, everything else in outline, and every button dips slightly under your thumb.
Usage
Use the primary (.primary) for the one action most people on that screen will take, which isn't always the one listed first. Most players join a game rather than host one, so on Home Join a game is filled and Create game is an outline.
Use a secondary (.secondary) for every other action. There is no third style: a screen that seems to need one has too many actions.
Use an icon button (.home-icon-btn, .modal-x) where a glyph says it faster than a word: the header's key, mode, help and back, a sheet's close, the refresh on Public games.
Use a text button (.rec-link, .text-btn) for a quiet side path or a way out: "I already have a code", "Not now", "I'm new, let's play".
Don't make a button round, and don't make it a pill. Square = tap, pill = read, circle = rank. No button is ever circular, because circles are ranks.
Anatomy
| Kind | Class | Size | Fill and edge | Type |
|---|---|---|---|---|
| Primary | .primary | min-height 52px, padding 15px 18px, radius 12 | --fg fill, --ground ink, lift 0 2px 6px --shadow-lift | 700 16px; .big is full width at 17px |
| Secondary | .secondary | min-height 52px, padding 13px 16px, radius 12 | transparent, 1.5px --rule edge, --fg text | 600 15px |
| Icon button | .home-icon-btn, .modal-x | 44×44, radius 12 | transparent, 1px --rule edge, --fg-soft glyph | 20px glyph; the close X is 16px |
| Icon and label | .icon-secondary | 44px tall, radius 12 | 1px --rule edge, --fg-soft | 600 14px, 6px between glyph and label |
| Round action | .tv-btn (trivia's Hint and Skip) | 44px tall, radius 12 | 1.5px --fg edge, --fg text | 700 15px |
| Text button | .rec-link, .text-btn | .text-btn min-height 44px | text only, --fg-soft | 500 14px (.rec-link), 600 14px (.text-btn) |
| Confirm pair | .btn-confirm | min-height 52px, radius 12, two equal widths | Cancel: 1.5px --rule outline. The action: .primary-confirm (filled) or .danger (rose edge and text on --danger-wash) | 600 15px |
Every kind is a real <button>; Hint, once used, becomes a plain label. Glyphs are inline SVG strokes in currentColor, so they take the button's colour in every state.
States
| State | What the player sees | Applies to |
|---|---|---|
| Rest | as in Anatomy | every kind |
| Hover (pointer only) | glyph or text goes to --fg; the edge goes to --rule-strong (.home-icon-btn) or --fg-soft (icon and label) | .home-icon-btn, icon and label, text buttons |
| Pressed | scale(0.98) and brightness(0.94) over 0.07s | primary, secondary, danger, icon buttons, icon and label, .text-btn, segments, room rows, standings |
| Focus | 2px ring, offset 2px: --fg on most buttons, --focus on .home-icon-btn | every kind |
| Disabled | opacity 0.4 (primary, secondary, danger, icon and label); 0.45 (Public games refresh); 0.5 (Hint and Skip) | |
| Busy | the label says what's happening and the button disables: "Rolling…", "Looking…", "Setting up…" | primary, Create game |
| Soft | opacity 0.55 and still tappable (.rc-soft) | Find my account, until six characters are typed |
| Done | an outline with a check and bold --fg text: "Saved to Photos", "Saved to Notes" | the two save buttons |
| Attention | 1.5px --fg edge and a 12px dot; the dot pops (240ms) and rings twice (900ms) once per visit, then rests | the key, until the code is saved |
Behaviour
- The press-dip is shared: every button in the list above shrinks to 98% and dims slightly, over 0.07s. Standings rows press as one piece, card and all, through
:has()(scale(0.99),brightness(0.97), 0.09s). Tiles never dip; see Tile. - Guide, don't block. Buttons stay enabled and point at what's missing. Find my account looks soft until six characters are in, but tapping it early focuses the code tiles and, if anything is typed, says "Your code has six characters."
- No connection. Join a game, Create game and the Compete cards disable, the hint under them says why, and Play solo stays live: the board and the dictionary are on the phone.
- Confirm in place. Taking a player out or making a new room code turns the row or button that asked into a question over two equal buttons: no modal, no layout shift. When taking a player out, Cancel is a bigger target than the X that opened the question.
- Rose is for removal. "Take out" is the
.dangeraction and fills solid rose only while pressed. "New code" makes something rather than destroying it, so its confirm is the filled primary. - No help button during a live round. The clock keeps running behind a sheet, so offering help mid-game is a trap: you either lose seconds or learn nothing.
Accessibility
| Pair | Dark | Light |
|---|---|---|
--ground on --fgthe primary's label on its fill | 12.70AAA | 12.70AAA |
| Pair | Dark | Light |
|---|---|---|
--fg-soft on --groundicon buttons and text buttons | 6.36AA | 4.78AA |
- Target size: 44px is the floor. Primaries, secondaries and confirms are 52px tall; icon buttons are 44×44. One button misses it; see Known issues.
- Names: icon buttons carry an
aria-labelthat says what happens: "Home", "Close", "Tutorial", "Check again", "Switch to light mode", and "Your code, not saved yet" while the key asks for attention. - Busy states are words, so a screen reader hears "Rolling…" or "Looking…", not a spinner.
- Focus is always visible on
:focus-visible: the--fgring is 12.70:1 against the ground,--focusis 5.27:1 (dark) and 5.38:1 (light). - Reduced motion: no movement on press, only a dim to
brightness(0.9). The key's dot appears without its pop or ring.
Content
- A verb that says what happens: "Create game", "Join a game", "Find my account", "Take out", "Play again". Never "Submit".
- Keep the name through the flow. The button that opens a step and the button that finishes it should agree (see Known issues for the one place they don't).
- Sentence case. Busy labels end in an ellipsis.
- Text buttons never carry icons. The check on "Saved to Photos" sits on an outlined secondary, not on a text link.
Why it's built this way
Known issues and gaps
- One tap target under the floor.
.text-btn.small("Clear" in the word lookup's history) is 32px tall against the 44px rule. Fix:min-height: 44pxwith the same text. Planned for 3.23.7 (3.23.6 audit). - Two focus colours. Six focus rules use
--gold(which resolves to--fg) and two use--focus; the spec says--focus. Planned for 3.23.7. - The spec still names a 36px close. The spec's button rules say the one icon-button exception is a 36px sheet close; the shipped
.modal-xis 44×44, so there is no exception. The code wins. - Not every button dips. Hint and Skip (
.tv-btn), Cancel and New code (.btn-confirm), the Compete cards (.cc-card) and the text links (.rec-link), among others, aren't in the shared press list, though the spec says every button except tiles dips. - Three disabled opacities: 0.4, 0.45 (the refresh) and 0.5 (Hint, Skip and the Compete cards).
- Two primaries carry a glyph, and nothing says why. "Play again" (solo results) and "Next round" (room results) lead with a replay arrow (
.primary.with-icon); every other primary in the game is text only. Everywhere else a glyph in a button carries something the label doesn't: a state (the check on "Saved to Photos"), or the round's own controls (the bulb on Hint, the skip mark). The replay arrow repeats the label. Proposed for 3.23.7: drop it, so primaries are text only. - One name breaks across a flow. Home's "Create game" opens a sheet whose button reads "Create room".
- The button CSS still reads legacy names (
--cta,--tray-line,--paper,--paper-dim,--gold). They resolve to the canonical roles; the rename is planned (see Color).
Related
- Tile: the one control that doesn't dip
- Segmented toggle: choosing between options, not acting
- Sheet: where the close button and footer primary live
- Field: guide, don't block, from the field's side
- Color: the tokens and the focus hue
Changelog
| Version | Change |
|---|---|
| 3.22.0 | Play on Home: one primary (Join a game), with Create game and Play solo side by side under it |
| 3.21.2 | Find my account stays tappable and looks soft until all six characters are in |
| 3.21.0 | Save to Photos (primary) and Save to Notes (outline) side by side; each becomes an outlined "Saved to …" with a check |
| 3.11.3 | The Public games refresh moves from a 36px one-off to the 44×44 icon button |
| 3.11.0 | Join, Create and the Compete boards disable with no connection; Play solo stays live |