Persona

Accessibility

What the design system guarantees, and where the rest comes from.

Updated Sep 22, 2026

This page is what the design system guarantees, and it is deliberately short. Focus, contrast, and the motion vocabulary are settled once and apply to everything. Everything else — keyboard behavior, roles, and the name your control announces — comes from the element you build on and from the component you chose, and those are on each component’s own page.

Focus is visible everywhere because it is defined once

Section titled “Focus is visible everywhere because it is defined once”
  • One rule, in global.css, applies to anything focusable. A component does not opt in.
  • It is an outline rather than a ring, so it follows border-radius and costs no box-shadow.
  • It sits outside any cascade layer on purpose, so a component that genuinely needs a different ring has to say so with !important — which is the right amount of friction.
  • Before it, there were three rings — an accent outline on content links, Chrome’s own default in the sidebar, a translucent blue on buttons — plus five components with their own and three different offsets.

Keyboard behavior comes from the element you build on

Section titled “Keyboard behavior comes from the element you build on”
  • Checkbox, Radio and TextArea are native inputs. Focus order, activation and typing come from the browser, and no component code is involved.
  • Select, Combobox, Dropdown Menu and Tab Bar are custom widgets. Each implements its own keyboard contract, and each of those pages states it.
  • There is no shared keyboard layer in PDS. Nothing operates a component on its behalf, which is why a page that says only “support keyboard navigation” has told you nothing.
  • Where a component’s keyboard contract is non-obvious, it is on that component’s page and it is specific — a radio group is one tab stop, not several; a menu returns focus to its trigger.

The palette reports its own contrast, including where it fails

Section titled “The palette reports its own contrast, including where it fails”
  • Every swatch on Color carries its computed WCAG 2.1 contrast ratio against black and white, derived from the token rather than recorded by hand.
  • Swatches that reach 4.5:1 against neither are marked △, and the page states how many there are.
  • That is a disclosure, not a pass. Mid-tone steps do fail, and the palette says so rather than picking the least-bad option and staying quiet.
  • PDS does not claim a WCAG conformance level, and this repo contains no record of an audit or of screen-reader testing. Contrast is the one thing computed here; everything else is unverified. Replace this sentence when there is something to replace it with.
  • There are three durations: --motion-fast at 100ms for hover and focus color, --motion-base at 150ms for a state change in place, --motion-slow at 300ms for travel.
  • Two easings: --motion-ease and --motion-ease-move.
  • Tailwind’s transition defaults are bound to the tokens, so transition-colors with nothing else on it is already the system’s timing.
  • React islands read the same values through src/constants/motion.ts, so the two never drift apart.
  • prefers-reduced-motion: reduce collapses all three durations at the token level, so a reader who has asked for less motion still gets every state change and none of the travel — the sidebar still opens, the theme still switches, and both arrive at their final value immediately. Because the CSS, Tailwind’s utilities and the React islands all resolve the same tokens, one rule reaches all three.

No control’s accessible name is provided

Section titled “No control’s accessible name is provided”
  • A control’s accessible name is never automatic. Some inputs carry their own label, some accept aria-label, and two have neither.
  • FormField is where that relationship is set out in full. The short version: Checkbox and Radio name their own option; Select, Combobox, Slider and Toggle can be named invisibly with aria-label; and SwatchPicker and TextArea have no other mechanism at all, so for those two FormField is required rather than recommended.
  • There are two sanctioned places in PDS where a control has no visible label of its own — a configField, and a row-selection checkbox in a Table. In both the control borrows a name. Neither goes without one.
  • Roles are yours too. The system ships no ARIA.