Accessibility

The WCAG 2.2 AA rules the kit holds for screens, the contrast tokens, the keys that hold everywhere, and three pre-release checks.

Rules

The bar is WCAG 2.2 AA and the whole admin is operable from the keyboard. That is not only an obligation: shop-floor staff work faster by key than by mouse. Most rules are held by the components themselves — which is why screens are assembled from them and not from copied classes, where the bar quietly drops.

  • Everything works from the keyboard and focus is always visible — the ring is never turned off.
  • Colour never carries meaning alone: a state is accompanied by text or shape. “Overdue” is spelled out, not left to a red dot. The same holds for charts and operation colour dots.
  • Headings go level by level without skipping — screen readers navigate by them.

Contrast

Contrast is not judged by eye — the palette is measured for it, so it is enough to reach for the right token. A colour outside the tokens carries no such guarantee.

  • Text at least 4.5 : 1. --ink, --ink-2 and --ink-3 hold AA on white and on --surface-2 alike, in the light and the dark theme.
  • --ink-5 is for separators and disabled states only, never for readable content — it does not reach AA (1.49 : 1 on white) and is not meant to. The lightest ink that may still be read is --ink-4, at 5.85 : 1.
  • Non-text elements need at least 3 : 1: the outline of an input, a switch and a checkbox therefore take --border-strong, not --border. With the ordinary outline the edge of a field would vanish on a light surface.

Focus and keyboard

The focus ring is a token rather than each component's own idea of one: --focus-ring is the width, --focus-ring-offset the gap, and the colour is --accent — so the ring follows the chosen accent family. The gap between the control and the ring is painted in the page colour, which is what makes the ring read on a dark button and a light one alike. A control standing on its own takes the ring outside it; a full-width row inside a scrolling list takes it inside, because that list would otherwise cut it off. outline: none without a replacement is never used — it is the fastest way to close a screen to keyboard users.

Click here and walk both elements with Tab — the ring has to show on both.

Five keys hold across the whole admin. A screen does not redefine them — a relearned key costs the user more than a missing one.

Keyboard shortcuts
KeyBehaviour
TabThe order matches the visual order; hidden elements drop out of it.
EscCloses the side panel, the modal and an expanded menu; focus returns to the element that opened it.
↑ ↓Movement inside a list and an expanded menu; focus does not escape the component.
Cmd/Ctrl KGlobal search from anywhere.
EnterSubmits a form; in a table it opens the detail of the highlighted row.

The touch target

A button is 34 px, an icon button 28, a checkbox 16. On a phone those are missed, and the ones missed most are a table's row actions and the close crosses. WCAG 2.5.5 and Apple's own rules both ask for 44 × 44 px.

The controls themselves do not grow. A larger button on touch would push the layout around and break the 34 px line the filter bar stands on — and a changed height is a major bump. What grows is the hit area: a transparent ::before with a negative inset, live only under pointer: coarse. Nothing is drawn, nothing reflows, and on a mouse not one declaration applies.

  • touch-target grows in both axes. For a control with room around it — a button, a switch.
  • touch-target-y grows only upward and downward. For controls standing shoulder to shoulder: row actions sit 2 px apart, so a sideways area would reach across the neighbour and the tap would go to whichever is later in the DOM. On a row holding Edit next to Delete, that is not a rounding error.
  • touch-row raises the whole row to 44 px instead. For a checkbox and a radio option: ::before generates no box on a replaced element, so an input cannot carry an area at all — and stacked options sit 6 px apart, where it would cover the neighbour's label anyway. The label wraps the control and its text, so the whole row is the target.

The utilities live in the package preset rather than in the documentation site's own stylesheet — otherwise a consumer would not get them and the classes the components carry would draw nothing.

One limit worth knowing: the area reaches outside the element, so overflow: hidden on any ancestor cuts it back to that ancestor's edge. Measured on a card carrying overflow-hidden: the control was left with the height it started with. Where the wrapper clips, the room has to come from the wrapper — the hit area cannot take it.

Semantics and assistive technology

A screen reader does not see the layout — it reads role, name and state. Whatever a screen says by shape has to be said by those too.

  • An icon button always carries an aria-label with a verb; a decorative icon is hidden from screen readers.
  • A modal and a side panel carry role="dialog", aria-modal="true" and a trapped focus; the rest of the page beneath them is marked inert so it cannot be tabbed into.
  • Notifications and validation go to aria-live="polite"; assertive is reserved for a critical error, because it interrupts whatever the screen reader is reading.
  • A table has real header cells with scope, otherwise a screen reader cannot tell which column a cell belongs to; a selected row carries aria-selected.
  • A field error is tied to its field through aria-describedby — not by the border colour alone, which a screen reader never reads.
  • Clickable area at least 32 × 32 px in dense tables, and 44 × 44 px on touch.
  • prefers-reduced-motion turns off shifts and animation; only the colour change remains.

The pre-release check

Three quick checks before a screen ships. They do not measure everything — but what fails them fails everything else too.

  1. Walk the screen with the Tab key alone: every element is reachable, focus is visible, and nothing loops.
  2. Zoom to 200 %: nothing overflows, nothing overlaps, no text is cut off, the table scrolls horizontally and the top bar does not fall apart.
  3. Picture the screen in greyscale: every state must be recognisable without colour.