| name | McDermott Accessibility |
|---|---|
| description | Use when reviewing or building for accessibility, WCAG compliance, ARIA, keyboard navigation, screen readers, focus management, color contrast, touch targets, alt text, semantic HTML, reduced motion, or any a11y concern. |
| version | 1.0.0 |
WCAG 2.2 Level AA is the minimum bar for every McDermott surface. Not negotiable.
- Body text: ≥ 4.5:1 against background
- Large text (18pt+ or 14pt+ bold): ≥ 3:1
- Non-text UI (icons, borders, focus indicators): ≥ 3:1
- Disabled controls exempt but still visually distinct
Non-text contrast (1.4.11) in practice — which border token: the 3:1 rule applies to any border or fill that is the only thing marking a control's presence and extent — text inputs, selects, textareas, unchecked checkboxes and radios, the toggle's off-track. Those use --border-strong (#8F8F99 light / #6B6BD0 dark), which clears 3:1 against both surface and page in both themes — light is at the floor on purpose (3.20:1 on surface, 3.00:1 on page; v1.12 lightened it from #85858F as far as the rule allows), so there is no room to lighten it further; --border-light and --border-button do not (1.2–2.4:1) and must never bound a bare control. Exempt by the standard, so --border-light/--border-button remain correct there: decorative edges (cards, separators, sheet and panel borders — the surface fill and content identify them) and text-labeled buttons (the label identifies the control). Checkboxes/radios already pass via their 2px --text-primary border; focus rings pass via --focus-ring. Surface-vs-page separation (card on canvas) has no WCAG requirement — it's an aesthetic hierarchy choice, and dark mode's navy-on-navy is deliberately subtle.
Never rely on color alone to convey state. Pair color with icon, label, weight, or pattern.
- Every interactive element reachable via Tab, operable via Enter/Space
- Tab order follows visual reading order
- No keyboard traps — Tab always exits eventually
- Focus trap acceptable (and required) inside modals — Escape exits
- Skip-to-main-content link as the first focusable element on every page
- Custom components match native keyboard expectations:
- Tabs: Left/Right arrows + Home/End
- Menus: Up/Down arrows + Enter to activate
- Comboboxes: typeahead + arrow keys
- Sliders: arrow keys ± Home/End
- 2px outline at
--focus-ring(blue light / teal dark) with 2px offset - Use
:focus-visiblenot:focusso mouse users don't see rings on click - Never
outline: nonewithout a visible replacement (most common a11y bug) - Focus indicator meets 3:1 contrast against adjacent background
ARIA is the last resort. Use semantic HTML first:
<button>not<div role="button"><a href>not<div onclick><nav>,<main>,<aside>,<header>,<footer>for landmarks<h1>–<h6>in correct hierarchy (don't skip levels)<label>for every form input
- Live regions (
aria-live="polite"|"assertive") for dynamic content — toasts, AI streaming, validation errors aria-expanded,aria-controls,aria-selectedon disclosure widgetsaria-current="page"on the active nav itemaria-describedbyto link inputs to helper text and errorsaria-hidden="true"on purely decorative icons; meaningful icons need an accessible label
- Non-decorative images have meaningful
alt. Decorative usealt="". - Icon-only buttons need
aria-labelor visually-hidden text. - Visually-hidden utility:
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
- Don't use
display: noneto hide content from sighted users that you want screen readers to read.
- Min 44×44px (iOS) / 48×48dp (Android)
- ≥ 8px spacing between adjacent targets
- McDermott button (140×36) — pad the click target if visual size is smaller
- Respect
prefers-reduced-motion: reduceon every animation - Replace movement with opacity-only transitions — never
transition: none - Auto-playing video, parallax, decorative animations all gate on this preference
- When motion is appropriate and how to add it: see
_core-requirements.md('Adding motion')
- Every input has a programmatic label (
<label for>oraria-label) - Errors use
aria-invalid="true"andaria-describedbypointing to the error message - Required inputs use
required(with visible "(optional)" marking on optional fields per forms skill) - Don't auto-advance focus or auto-submit — keyboard users have no chance to review
Video has captions for spoken content. Audio has transcripts. Images have alt describing information value, not visual appearance.
- Tab through every interactive element with focus visible
- Operate every control with keyboard alone
- Automated checker (axe, Lighthouse) — zero critical issues
- VoiceOver (Mac) / NVDA (Windows) for primary user flow
- Verify color contrast on every text/background pair
- Toggle
prefers-reduced-motionand confirm no jank - Test at 200% zoom — content reflows, nothing cut off
The McDermott palette has known-good pairs (navy on white = 19:1, navy on teal = 7.4:1). Confirm any other pairing. The pale-bg + navy-text rule (theme-stable foreground) exists partly for contrast — never override. Focus rings use --focus-ring which flips per theme — test both.
- Touch targets ≥ 24×24 (SC 2.5.8 AA), ≥ 44×44 preferred. Pad with transparent space.
- Test VoiceOver (iOS) + TalkBack (Android) — touch SR gestures differ from desktop.
- Mobile keyboard covers content — on focus, scroll input into view via
scroll-margin-top+element.scrollIntoView(). - Never disable browser zoom (
user-scalable=nois an AA violation). Inputs needfont-size ≥ 16pxto avoid iOS auto-zoom. prefers-reduced-motionmatters more on mobile (motion sickness held close).- Focus indicators still matter — keyboard nav happens via Bluetooth keyboards and switch devices.
See responsive-and-mobile.md for the universal checklist.
outline: nonewithout a visible replacement ·<div onclick>instead of<button>/<a>- Color as the sole indicator of state · skipping heading levels (h2 → h4)
- Placeholder as the only label · meaningful icons without an accessible label
- Decorative icons without
aria-hidden· auto-playing audio/video without user control - Disabling browser zoom · trap keyboard focus outside modals
- Treating WCAG AA as a stretch goal