Accessibility
AA contrast measured from the tokens on every build, a 44px floor for every target, a visible focus ring, and reduced motion as a designed version. Playing the board itself still needs a pointer.
How it works
The target is WCAG 2.1 AA: 4.5:1 for body text, 3:1 for large text (24px and up, or 19px bold) and for non-text UI such as focus rings. The contrast tables on this page are computed from the tokens when the docs are built, not asserted, and the places the system falls short are listed at the end rather than left out.
Most of the work is done by the foundations themselves:
- Two grounds with symmetric contrast. The roles swap between modes, so a pair that passes in one mode passes in the other. See Color.
- A 44px target floor on every control. See Layout and spacing.
- Reduced motion as a full design, not an off switch. See Motion.
- Colour plus words. Green and rose each mean one thing, and the classic board's caption also says what happened: "+3 pts", "already found", "go longer", "not a word".
Contrast
| Token | Dark · ground | Dark · card | Light · ground | Light · card |
|---|---|---|---|---|
--fgbody text | 12.70AAA | 10.14AAA | 12.70AAA | 13.75AAA |
--fg-softsecondary text | 6.36AA | 5.07AA | 4.78AA | 5.17AA |
--fg-fainttertiary | 3.19UI only | 2.55decorative | 2.29decorative | 2.48decorative |
--earnedearned (green) | 8.98AAA | 7.17AAA | 5.71AA | 6.18AA |
--dangerrefused (rose) | 8.52AAA | 6.80AA | 7.54AAA | 8.16AAA |
--focusfocus ring | 5.27AA | 4.21UI only | 5.38AA | 5.82AA |
WCAG contrast, computed from the tokens on every build. AA needs 4.5:1 for text, 3:1 for large text and UI.
Body text, secondary text and both semantic hues pass AA on the ground and on cards, in both modes. --fg-faint doesn't, so it's for non-text only; three rules still use it for text (see Known issues).
Type on a fill
Text on a semantic fill is always --ground, never white or black, and letters on tiles use the tile's own ink:
| Pair | Dark | Light |
|---|---|---|
--ground on --earneda word that scores, in the green pill | 8.98AAA | 5.71AA |
| Pair | Dark | Light |
|---|---|---|
--ground on --dangernot a word, and the clock's last 10 seconds | 8.52AAA | 7.54AAA |
| Pair | Dark | Light |
|---|---|---|
--tile-ink on --tilea letter on a resting tile | 12.70AAA | 12.70AAA |
| Pair | Dark | Light |
|---|---|---|
--select-ink on --selecta letter on a lit tile | 12.70AAA | 12.70AAA |
Hairlines
--rule, the hairline under rows and around outlined controls, sits below the 3:1 non-text target on purpose: 1.65:1 on the dark ground and 1.30:1 on the light one (--rule-strong is 2.61:1 and 1.82:1). The hairline look is a deliberate choice, and a hairline is decoration, never the thing that carries meaning.
Target size
The floor is 44px in the smaller dimension of every interactive element. Buttons and fields are 52px tall, icon buttons and the sheet's close are 44×44, and the segmented control, text buttons and Hint and Skip are at least 44px tall. The smallest tile in play, a 5×5 tile on a 320px phone, is 49.6px. The one control under the floor is listed in Known issues; the full table is in Layout and spacing.
Focus
Every focusable control shows a ring on :focus-visible: outline: 2px solid, outline-offset: 2px. It appears for keyboard focus, not for a tap.
The ring comes in two colours today. Of the 8 focus rules in the CSS, 6 use --gold (an alias of --fg: cream on the dark ground, navy on the light one), including the global button:focus-visible, input:focus-visible. Two use --focus, the system's blue: the header's icon buttons and the recovery code panel. Both colours clear 3:1 against the ground: --fg at 12.70:1, --focus at 5.27:1 (dark) and 5.38:1 (light).
Reduced motion
Under prefers-reduced-motion: reduce, tiles and the word pill change state without fading, pressed buttons dim without shrinking, sheets open in place, the key's dot shows without its ring, the podium opens built with the crown on and no confetti, the update reel opens on its last screen, and the brand mark holds still on its hook. The test is that nothing the game says is said only through movement. The full table is in Motion.
Screen readers
What the code does today, counted in app.jsx at 3.23.6:
| What | How | Where |
|---|---|---|
| Names for icon-only controls | 28 aria-labels | "Your code" (and "Your code, not saved yet"), "Switch to light mode", "Close", "Home", the brand mark as "Outword" |
| Decoration kept out | 55 aria-hidden | icons inside labelled buttons, the confetti layer, the code tiles, and the hidden questions that size the trivia card |
| Dialogs | 4 role="dialog" with aria-modal and a label | the bottom sheet (labelled by its title), the help sheet, the update reel, "Played before?" |
| Status updates | 6 role="status" | "Copied", "Getting your code…", "Paste works too.", the toast, the welcome card, and trivia's verdict |
| Toggles | aria-pressed; one radiogroup with aria-checked | the mode and team segmented controls, and the row that shows a player's longest word on the board; Private and Public for a room |
| Expanding rows | aria-expanded | standings rows that open a player's words |
A few of these are worth copying:
- Codes are spelled out. The recovery code panel is labelled "Copy your code," followed by the code one character at a time, and the saved picture's alt text does the same, so a screen reader reads characters, not a made-up word.
- The trivia verdict is announced. A visually hidden status says "That's it:" with the answer on a solve, and "Not it." on a miss.
- The update reel has a text version. A visually hidden paragraph gives everything the reel shows, in one go.
- Sheets close from the keyboard. Escape closes the bottom sheets, the help sheet and the update reel. The X and the scrim close sheets too.
Why it's built this way
Known issues and gaps
From the 3.23.6 audit, plus what turned up while writing this page:
- The board needs a pointer. Tracing a word is a touch or mouse swipe across tiles that are plain
divs, with no keyboard or assistive-technology way to spell a word. The classic board's caption and pill aren't announced either; only trivia has a live status. The open item is a keyboard model: arrow keys to move, Space to select. - Faint text fails contrast.
--fg-faintreads 3.19:1 on dark and 2.29:1 on light, and three rules still use it as text. Planned fix (3.23.7): those become--fg-soft. - Two focus colours. 6 rules ring in
--gold, 2 in--focus; the spec says--focus. Planned fix (3.23.7): one ring everywhere, in--focus. - One target under the floor. "Clear" in the word log is 32px tall. Planned fix (3.23.7):
min-height: 44px, same visual text. - The spec documents an exception that's gone. It says icon buttons have one exception, a 36px sheet close. The sheet's close is 44×44, and the one-page reference already says "the modal close included. No exceptions." The code wins: there's no exception.
- Trivia's "not it" is colour on screen. A wrong answer shows as the traced word in the rose pill, with no words beside it; "Not it." reaches screen readers only. The classic board says "not a word" in its caption.
- Sheets don't manage focus. Apart from the restore sheet, which focuses its code field, a sheet doesn't move keyboard focus into itself when it opens, doesn't hand it back when it closes, and doesn't keep Tab inside it.
- Two fields have no label. The room code field in Join a game and the dictionary's field are named only by their placeholders ("CODE" and "type it"), which disappear as you type.
- Pinch-zoom is switched off. The page's viewport sets
user-scalable=no.
Related
- Color: the tokens behind every pair above
- Motion: reduced motion, move by move
- Layout and spacing: the 44px floor, control by control
- Tile: the board, and why it can't be played without a pointer yet