Accessibility Playbook

On this page

The floor: WCAG 2.1 AA + Section 508

Both — the Web Content Accessibility Guidelines (WCAG) 2.1 at level AA, and Section 508 of the Rehabilitation Act — are non-negotiable for government deployment. Every surface CRAIG ships — core panels, jurisdiction plugins, public-facing portals, print outputs — adheres to both standards or does not ship.

Validated against:

  • The NVDA, JAWS, and VoiceOver screen readers

  • Keyboard-complete operation (no pointer required for any action)

  • Reduced-motion preference honored everywhere

Focus is always visible

Focus indication is never removed for aesthetics. The rule is mechanical:

  • :focus-visible → 3 px gold ring (Statehouse) / 3 px terracotta ring (Foundation), 2 px offset

  • Focus order follows visual order. Tab moves through focusable elements in the order they read on the page.

  • Modals trap focus + return it to the trigger on close.

  • No outline: none — CSS reset that strips focus is rejected at PR review.

The 3 px ring color uses --accent (decoration token) — never carries text contrast (see Brand Identity, Palettes, and Typography).

As built, the branded ring is still rolling out: the shipped custom :focus-visible rule covers the case-detail tab strip (2 px --link, in components.css), and every other surface keeps the browser’s default focus ring — nothing strips it, so focus is visible everywhere. The 3 px --accent ring above is the contract new CSS adopts.

Color is never the only signal

Icon + label always accompany hue. Status conveyed three ways:

Status Hue Icon Label

Substantiated

red

"Substantiated"

Safe

green

"Safe"

Due soon

amber

"Due soon"

Overdue

red

"Overdue"

In progress

blue

"In progress"

Pending

gray

"Pending"

Why: red/amber/green read identically to many color-blind users and disappear in high-contrast mode. The icon and word carry the meaning; color is reinforcement.

Red is reserved for danger — never branding, never decoration. See Brand Identity, Palettes, and Typography § red is reserved exclusively for danger.

The keyboard model

Every action is reachable without a pointer. Tab order equals visual order, everywhere.

Key Action

⌘K / Ctrl+K

Open the command palette from anywhere

Esc

Close palette, modal, drawer, or popover — focus returns to the trigger

Tab / Shift+Tab

Move through focusable elements in visual order

← →

Move between tabs (case workspace, settings) — per the WAI-ARIA (Accessible Rich Internet Applications) tabs pattern

Home / End

First / last tab in a tablist

↑ ↓ + Enter

Move & select within the palette, worklists, and menus

Alt+↑ / Alt+↓

Reorder a panel or case section in the composable grid — drag-free

?

Open the keyboard-shortcut help sheet

This table is the contract each surface adopts as it ships. Built today: Tab/Shift+Tab traversal, Esc on the native <dialog> modals, and Enter activation on the case-detail tab strip (the mechanism the VPAT records). The command palette, arrow-key/Home/End tablist navigation, panel reorder, and the shortcut help sheet ship with their surfaces.

Composability is keyboard-complete, by contract. When panel and case-section rearrangement ships, the drag interaction must have a full keyboard equivalent (Alt+↑/↓), announced via an ARIA live region ("Moved Placement above Court"). Neither reorder path exists yet — both composition surfaces (Studio and Personalize) currently render placeholder text where the reorder controls will live.

Semantic landmarks + ARIA

Screen-reader users navigate by landmark. Every chrome region is labeled.

Landmark map (worker screen reference)

Landmark What it labels

skip-link

First focusable element on the page → jumps to #main-content

role=banner

App chrome · brand · ⌘K search · user

role=navigation

Breadcrumb · aria-current="page"

role=main

The case workspace / dashboard / focal surface

role=complementary

The persistent case spine

role=contentinfo

Footer · powered-by

The shipped chrome carries the first four rows (skip-link, banner, breadcrumb navigation, main — all in the shared base template); the complementary and contentinfo rows apply when the case spine and footer regions ship.

Component-level commitments

  • Tabs: role=tablist · aria-selected · arrow-key nav

  • Form errors: linked to inputs via aria-describedby — field-attached, on blur, never color-alone

  • Modals: focus trap + return; role=dialog · aria-modal

  • Tables: semantic <table> · sortable headers announce sort state

  • Alerts: role=alert for danger; live regions for async updates

  • SR-only context via .sr-only where visual shorthand needs spelling out

Contrast, motion, modes

Contrast

  • Body text ≥ 4.5:1

  • Large text + UI elements ≥ 3:1

  • Audited per release, both light AND dark

  • High-contrast mode ships alongside light/dark (required for state systems that demand it)

  • --accent (gold / peach / terracotta) is decoration only — it never carries text contrast

Motion

  • prefers-reduced-motion disables the skeleton pulse + caret blink

  • Entrance animations fall back to end-state when reduced-motion is set

  • No parallax. No background video. No autoplay anything.

Reading level

  • Constituent copy targets Grade 6. The built public reporting portal targets sixth-grade reading level, as will the Phase 11 family and foster portals when they are built. The Hemingway-app score is checked at PR review.

  • Caseworker copy uses glossary tooltips for unavoidable terms: statutory language, Adoption and Foster Care Analysis and Reporting System (AFCARS) field names, and Interstate Compact on the Placement of Children (ICPC) categories.

  • Labels carry through i18n — Spanish ~+20% length is accommodated, RTL not prevented.

Print output (court reports, AFCARS submission previews, National Child Abuse and Neglect Data System — NCANDS — submission previews) is designed as a distinct surface. No print stylesheet ships yet; these are the requirements each print surface meets as it lands:

  • All structural elements use semantic HTML; the screen + print stylesheets share semantics + diverge on layout only.

  • Page breaks are deterministic: section heads do not orphan; tables that span pages repeat the column header on each page.

  • Hyperlinks expand to footnoted URLs in print mode (since clicking is not available).

  • Print is monochrome-safe — anything that relied on color for meaning is replicated as label + icon in print mode.

Where these rules land in code

  • Focus indication: the contract target is one canonical shared :focus-visible rule; today the shipped custom rule lives in services/craig-web/static/css/components.css (tab strip) and other surfaces keep the browser default ring (see § Focus is always visible).

  • Keyboard model: implemented at the backend-for-frontend (BFF, craig-web) route + template layer + per-plugin (Contract 1) author responsibility.

  • Semantic landmarks: carried by the shared base template today; extending the F-021 template-safety lint (cargo xtask validate-template-safety) to verify landmark presence is planned, not yet built.

  • prefers-reduced-motion: every CSS animation MUST carry a @media (prefers-reduced-motion: reduce) override. Shipped CSS currently has no keyframe animations (only two brief transitions), and the enforcing build-time lint is planned.

  • Contrast: the shipped machine guard is the #643 test that --accent never carries text contrast (services/craig-web/src/theme.rs); the systematic per-pair WCAG-ratio scanner lands with #641. New tokens must declare both light + dark mode + pass contrast for body + UI use cases.

  • Print stylesheet: per-page-template + per-plugin author responsibility; PR review verifies.

Source + provenance

Edit this page · latest