Skip to main content
Accessibility in this design system is a constraint, not a review step. Components must be usable in bright sunlight on a cracked screen as readily as on a desktop in an office, and the thresholds below are what makes that checkable rather than aspirational.

Contrast: WCAG minimums and APCA

Two bars apply together, and both are enforced in the registry’s token build. The WCAG 2 minimums (owner, 2 November 2026) are a floor on the ratio; APCA 3.0 — the Advanced Perceptual Contrast Algorithm — is the perceptual bar the palette is solved against. The two models disagree on some pairs, so a colour has to clear both. Body and non-body text are measured on both base and surface, in both themes. The APCA bars sit beside them:

Five button labels hold 4.5:1, not 7:1

One exception is named and owner-confirmed. These five roles hold 4.5:1 on their fill instead of 7:1 (APCA Lc 75 still applies):
  • primary-foreground
  • brand-accent-foreground
  • app-accent-foreground
  • sidebar-primary-foreground
  • destructive-foreground
They are button labels, and the fill is each brand’s accessible (-aa) tier, which is 4.5:1 under white by definition. Holding 7:1 would darken every brand’s action colour, so the exception stays until the owner rules otherwise. It covers those five roles and nothing else: running text and a label on any other fill need 7:1.

Why two bars

Sodalite’s published darkHex is #3D5AFE. Against a near-black background it passes a WCAG ratio check and scores APCA Lc -25.8 — below even the Lc 30 floor for non-text UI. The ratio says fine; the perceptual model says unreadable; where the two disagree, the perceptual one is right, which is why the ratio is a floor and not the whole test. A ratio check will also mislead you about polarity. It is not polarity-aware, so it tests a dark-mode colour against a light background and demands a threshold on both — and satisfying that pushes both values to mid-tone and serves neither theme.

Testing a new pairing

  1. Test in both light and dark.
  2. Check the WCAG ratio against the tier above and the APCA Lc against its row.
  3. Check the pairings that actually ship — --muted-foreground on --muted, --foreground on --card — not only foreground on background.
The registry exposes the WCAG ratio over MCP as mzizi_check_accessibility, which computes the ratio locally, gives the AA, AAA and 7:1 verdicts, and does not compute APCA or simulate colour-vision deficiency. See the MCP server.

Touch targets

Every interactive control keeps a 49px hit region, on every device, desktop included. That is the Bundu floor (owner, 2 November 2026): 7 × 7, above Apple’s 44 pt. Nothing interactive has a smaller hit region. On touch, controls are also drawn at 49px; on a desktop a button-like control may be drawn smaller (28 to 40px) and keeps the 49px region through a transparent pseudo-element. The full rule, the sizes and the migration are on the Bundu floor. The space around controls has a floor too: 14px inset, 7px gaps and 1.5 body line height, on the spacing floor. Use the size roles rather than h-12 or h-14:
For a small visual element such as a checkbox indicator, extend the clickable area with padding rather than shrinking the target:

Keyboard navigation

Interactive components use Radix UI primitives, which handle:
  • Focus management — trapping focus in modals, restoring it on close.
  • Arrow-key navigation — within menus, tabs, radio groups and comboboxes.
  • Escape to close — dialogs, dropdowns, popovers, sheets.
  • Enter and Space to activate — buttons, checkboxes, toggles.
  • Home and End — first and last item in a list.

Focus indicators

Every focusable element shows a visible ring, 2px with a 2px offset, using the --ring token (cobalt):
Never remove it.

Screen readers

Semantic HTML

Use the element that means what you intend: <button> for actions rather than a div with an onClick, <a> for navigation, <nav> and <main> as landmarks, and heading levels in order with none skipped.

ARIA

Radix components carry the ARIA attributes they need. For your own:
N3 brand components get a LiveRegion from the harness rather than building their own — see placing a component.

Testing

Test with at least one screen reader: VoiceOver on macOS and iOS, NVDA on Windows, TalkBack on Android.

Colour independence

Never use colour as the sole carrier of meaning. Pair it with a text label (“Error: invalid email”, not a red border alone), an icon, or a pattern. The mineral palette was chosen with common colour-vision deficiencies in mind, which reduces the problem but does not remove it — two minerals that are distinguishable to you may not be to a reader.

Checklist

  • Contrast meets the WCAG minimum (7:1 body, 4.5:1 non-body, 3:1 UI) and the APCA threshold, in both themes
  • Every control has a 49px hit region, and text is 17px body and 14px at the smallest
  • Every interaction is reachable by keyboard
  • Focus indicators are visible and unmodified
  • A screen reader announces the component’s purpose and state
  • Colour is not the only signal
  • Heading levels are sequential