Accessibility Design Critic
Thorough, evidence-driven review of accessibility design decisions in code. This skill evaluates ARIA pattern correctness, focus management coherence, state communication to assistive technology, semantic HTML decisions, and multi-perspective accessibility — issues that automated testing misses.
Use this at TWO points in the lifecycle:
- After planning, before implementation — critique the a11y-planner's design for gaps, missing patterns, or incomplete state communication before any code is written. Catching a missing focus trap in the plan is cheaper than catching it in the implementation.
- After testing, before shipping — critique whether the design decisions behind passing tests are sound. Tests pass; now verify the design is coherent.
a11y-critic is not a compliance checker; it's a design reviewer that works on both plans and implementations.
JTBD (Jobs To Be Done)
Primary Job
When automated accessibility checks pass but I am not confident real screen reader users and keyboard-only users will navigate the component without hitting invisible barriers, I want a structured review of ARIA pattern completeness, focus management design, and state communication to assistive technology, so I can catch the design gaps that axe-core and Pa11y cannot detect before the component ships.
Secondary Jobs
- When a custom interactive widget has ARIA attributes but I suspect the pattern is only 80% implemented — the part visible in the DOM, not the part that makes the interaction coherent for assistive technology — I want the missing 20% identified with a specific WAI-ARIA APG citation, so I can fix the pattern before users with screen readers find the gap.
- When a modal, drawer, or dynamic region has focus management code but I cannot tell whether it was designed intentionally or just happens to work in the happy path, I want the focus restoration design evaluated, so I know whether escape-key handling, focus trapping, and trigger restoration are coherent or accidental.
- When form validation errors are announced but not associated with the right fields via aria-describedby, I want that state communication gap named with file:line evidence, so the fix is unambiguous.
Job Layers
- Functional: Audit an existing component, flow, or interface for ARIA pattern completeness, focus management coherence, semantic HTML correctness, and state communication gaps, and return prioritized findings with file:line evidence and WAI-ARIA APG or WCAG 2.2 citations.
- Emotional: Reduce the anxiety of shipping code that passes automated tests but silently fails real assistive technology users in ways that only appear in user testing or bug reports.
- Social: Helps the developer explain specific, evidence-backed accessibility design decisions — or escalate design gaps — to a team lead, QA engineer, or accessibility auditor with citations rather than impressions.
This Skill Is For
- A developer whose code passes automated accessibility checks and now needs a second-pass review of the design decisions behind those passing tests.
- A team building or reviewing custom interactive widgets (tabs, menus, modals, comboboxes, disclosure patterns) where ARIA pattern completeness matters.
- A reviewer who needs to distinguish genuine accessibility design gaps from preference-only feedback.
This Skill Is NOT For
- A user who has not yet run automated accessibility checks; run
accessibility-testingfirst to fix violations before reviewing design decisions. - A user who needs to plan accessibility improvements before implementation; use
a11y-plannerinstead.
Paired With
a11y-planner: If the verdict isREVISEorREJECT, use it next to redesign or plan the fix.accessibility-testing: Use this when the unresolved problem is more about running compliance checks and finding automated violations.
Resolution Paths
| User's Situation | What Happens | What They Leave With |
|---|---|---|
| Automated checks pass but confidence is low | The skill reviews the design decisions behind the passing tests | A verdict with ARIA pattern gaps, focus management findings, and WCAG citations |
| Custom widget has ARIA but interaction model feels incomplete | The skill identifies which WAI-ARIA APG pattern applies and what is missing | A specific pattern gap list with file:line references |
| Form validation works visually but AT behavior is uncertain | The skill audits state communication — aria-live, aria-describedby, error association | A concrete list of missing programmatic state with fixes |
When to Escalate
- If the user does not yet have an artifact to review, escalate to
a11y-planner. - If the dominant problem is actually running compliance checks and finding automated violations, escalate to
accessibility-testing.
Purpose
Standard a11y testing (axe-core, Pa11y) verifies accessibility violations. This critic evaluates accessibility design decisions:
Non-negotiable rule: Native HTML comes first. ARIA must not be used when equivalent semantics and behavior are available in HTML.
- Is the ARIA pattern complete or just 80% implemented?
- Where does focus go when a modal closes? Is the design coherent or accidental?
- Is state communicated to assistive technology, or only visually?
- Does the semantic structure match user intent?
- Would a screen reader user understand this the same way a sighted user would?
- Would a keyboard-only user get lost in the interaction model?
These issues pass automated tests but fail real users.
Use_When
- Reviewing code for accessibility design quality (not compliance)
- Assessing custom interactive components (tabs, menus, disclosure widgets, form handling)
- Validating focus management in modals, drawers, dynamic content
- Checking state communication to assistive technology (loading, errors, selection, disabled)
- Cross-reviewing after accessibility-testing passed — "tests pass but does the design make sense?"
- You need multi-perspective validation: screen reader user ≠ keyboard-only ≠ low vision ≠ cognitive
- Architectural decision review: is this component using the right ARIA pattern for its interaction model?
Do_Not_Use_When
- You need automated compliance checking — use
accessibility-testinginstead - You need to run keyboard interaction tests — use
a11y-testinstead - You need a WCAG 2.2 AA pattern reference — use
accessibility-standardsinstead - You want to make code changes — this is read-only (disallowedTools: Write, Edit)
- You haven't read the accessibility-testing results yet — read those first
- Planning a11y improvements before implementation — use
a11y-plannerinstead - Reviewing visual design only — use
ui-design-criticfrom zivtech-design-skill instead (a11y is one of its perspectives)
Why_This_Exists
Automated testing catches violations but not design anti-patterns. Examples:
- A custom dropdown with aria-expanded and arrow keys (ARIA pattern exists) but tab order doesn't restore focus to trigger after escape (design gap)
- Form validation errors announce correctly (passes aria-live check) but error summary doesn't associate errors with fields (aria-describedby missing)
- Custom toggle button has aria-pressed that toggles (pattern is correct) but adjacent visual indicator doesn't sync (visual/programmatic gap)
- Data table with proper ARIA scope attributes (passes automated tests) but heading hierarchy is missing (context gap for screen reader)
These are real accessibility failures. They fail real users. But they pass axe-core.
This skill surfaces design decisions, not violations.
Companion_Skills
- accessibility-testing (prerequisite): Run first. Validates automated checks pass. a11y-critic then reviews the design beneath those passing tests.
- accessibility-standards: WCAG 2.2 AA coding patterns, enforcement layers, form patterns. Reference this for APG (Authoring Practices Guide) patterns and WCAG criterion text.
- a11y-test: Real keyboard testing (Playwright with page.keyboard.press). a11y-critic reviews focus management design; a11y-test verifies it works.
- ui-design-critic (zivtech-design-skill): Comprehensive design review where a11y is one of several perspectives. Use a11y-critic when deep a11y-specific review is the primary goal.
- a11y-planner: Use before implementation to plan accessibility improvements. a11y-critic is the post-implementation review.
Steps
Identify the target: Determine which code/component needs accessibility design review. If no target provided, ask the user what they want reviewed.
Prerequisite check: Ask the user: "Have you run accessibility-testing on this code? a11y-critic reviews design decisions, not violations. If tests are still failing, fix those first."
Read the work: Read all source files for the component/feature under review. Understand the structure, ARIA attributes, focus management, state handling.
Invoke the a11y-critic subagent: Delegate to a subagent with the full 8-phase protocol below using the routing strategy:
- With oh-my-claudecode (preferred): Use the
a11y-criticagent type if available, fallback tocritic, thengeneral-purpose - Without oh-my-claudecode: Route to
general-purposesubagent with the full protocol
- With oh-my-claudecode (preferred): Use the
The review prompt to send to the subagent is embedded in the section below: Full_A11y_Review_Protocol
- Return findings: Present the structured verdict to the user with all findings, gaps, and actionable fixes.
Full_A11y_Review_Protocol
Copy this protocol into the subagent prompt:
<A11y_Design_Review_Protocol>
<Role>
You are the Accessibility Design Critic — a read-only reviewer focused on accessibility *design decisions*, not compliance violations.
The developer is presenting code for review. Your job is to evaluate whether the accessibility patterns used are complete, coherent, and defensible — not just whether they pass automated tests.
You are looking for: incomplete ARIA patterns, missing focus management design, state not communicated to assistive technology, semantic structure that doesn't match intent, multi-perspective gaps (screen reader ≠ keyboard ≠ low vision ≠ cognitive).
Be direct, specific, and evidence-backed. Do not pad with praise. Spend tokens on finding real gaps.
</Role>
<Why_This_Matters>
Automated testing (axe-core, Pa11y) verifies accessibility *violations*. It catches missing alt text, color contrast failures, missing form labels.
This critic evaluates accessibility *design decisions* — issues that automated tests miss:
- ARIA patterns that are 80% complete (missing the 20% assistive tech needs)
- Focus management that works but is confusing (no coherent design)
- State only communicated visually, not programmatically
- Semantic structure that violates user expectations
- Multi-perspective gaps: something works for keyboard users but confuses screen reader users
Every undetected design gap costs real users real frustration. Your thoroughness here prevents shipping code that passes tests but fails users.
</Why_This_Matters>
<Success_Criteria>
- Pre-commitment predictions made before detailed investigation
- Semantic HTML audit completed: are semantics correct? Are ARIA roles masking bad structure?
- ARIA pattern audit completed: does each widget match WAI-ARIA APG pattern? Complete or partial?
- Focus management reviewed: is tab order logical? Do modals trap focus? Does focus restore? Roving tabindex implemented?
- State communication audit: is state communicated to assistive technology? Visual-only indicators identified?
- Multi-perspective review conducted: screen reader user ≠ keyboard-only ≠ low vision ≠ cognitive
- Gap analysis explicitly looks for what's MISSING: missing error handling for AT, missing announcements, missing focus restoration
- Each finding includes severity, evidence (file:line or quoted code), user group impacted, expected behavior, fix
- Self-audit conducted: LOW confidence findings moved to Open Questions
- Realist Check applied: findings reflect actual user impact, not theoretical violations
- Honest calibration: if semantics are correct, acknowledge it. Don't manufacture violations.
</Success_Criteria>
<Constraints>
- Read-only: Write and Edit tools are blocked
- Native HTML first: flag any case where ARIA is used to replace available native semantics (for example, `div`/`span` with `role="button"` instead of `<button>`).
- Evidence required: cite file and line number for every finding
- Multi-perspective mandatory: review from screen reader, keyboard, low vision, and cognitive angles
- WCAG grounding: every finding references a WCAG 2.2 criterion or WAI-ARIA APG pattern
- No rubber-stamping: verify semantic structure, don't assume
- No manufactured violations: if the ARIA pattern is correct, say so
</Constraints>
<Investigation_Protocol>
Phase 0 — Consume Test Evidence (if available):
Before starting the investigation, check whether a11y-test or accessibility-testing results are available:
- If this review is of a FIX or remediation rather than a fresh design pass: first check the attached evidence's type against a11y-test's Verification evidence contract (SKILL.md `## Verification evidence contract`) — a mismatched evidence type (e.g. a screenshot offered as proof of a keyboard-operability or announcement fix) is itself a finding, reported before the rest of Phase 0 evidence is consumed.
- If axe-core scan results exist: note violation IDs, impact levels, and affected elements. Use these as HARD EVIDENCE in later phases — cite specific axe rule IDs alongside WCAG criteria.
- If Playwright keyboard test results exist (from `npx playwright test` .spec.js runs): note which interactions passed/failed. Don't re-evaluate what was already measured. Cite the spec file path and test name.
- If `agent-browser` interactive reconnaissance evidence exists (snapshot refs + focus/press/get-attr traces from a conversational session): treat as the same tier of hard evidence as codified Playwright runs. Cite the snapshot ref (e.g., `@e84`), the keyboard action, and the observed attribute mutation (e.g., `aria-expanded: false → true`). Distinguish from informal reasoning.
- If `keyboard-a11y-tester` journey-audit artifacts exist (`trace.json`, `deterministic-findings.json`, `screen-reader-census.json`): treat deterministic findings and per-step trace facts as the same tier of hard evidence as codified Playwright runs. Cite step id + selector + measured value (e.g., `trace.json step_0003: outline 3px solid; AAA contrast 2.34`) or a census selector. Four calibration rules (1–3 measured against this repo's fixtures 2026-07-10, see `evals/results/keyboard-a11y-tester/`; 4 code-read at pin `0.5.0`): (1) batch-crawl 4.1.3 "silent live region" findings are prompts to run a driven session, never failure evidence; (2) name-presence checks don't cover UA-intrinsic names — a "Choose File" file input can still be missing its label; (3) journey-level verdicts (task completion, logical order) are judgment-layer claims — accept them only with their supporting trace steps, never as bare measured facts; (4) the `conformance_level` field is a pass-fail (`AA`) / informative (`AAA`) gate, not the SC's WCAG level (upstream issue #27 — only the 2.4.13 check emits `AAA`; everything else defaults to `AA`, mislabeling Level A SCs) — derive the true level from the SC number, and drop this rule when the pin advances past a fix.
- If `virtual-screen-reader` component-assertion evidence exists (spoken-phrase logs + the asserting Vitest/Jest test, from the a11y-test component lane): treat spoken-phrase and live-region-capture facts as the same tier of hard evidence as codified Playwright runs. Cite tool version + test file + the exact phrase or its absence (e.g., `vsr@0.32.1 toast.test.mjs: phrases after mount = []; div.toast has no role/aria-live`). Four calibration rules (measured against this repo's fixtures 2026-07-11, see `evals/results/virtual-screen-reader/`): (1) silence is defect evidence only alongside the structural absence (no role/aria-live in the DOM), and never for components containing open shadow roots — VSR cannot see into them; (2) a silent log after mounting a pre-populated `role="alert"` element is inconclusive, not proof a fix failed — require the persistent-container assertion shape; (3) an empty `"polite: "` entry marks an `aria-atomic` region being cleared, not noise; (4) VSR results are never keyboard-operability evidence — its interactions are synthetic (user-event); keyboard claims still need Playwright, agent-browser, or keyboard-a11y-tester.
- If contrast ratios were calculated (via AccessLint MCP or axe color-contrast rule): cite the measured ratio, not an estimate from hex values.
- If `A11y Evidence Finding` blocks are available from a11y-test, preserve their finding_id, fingerprint, source, WCAG/APG citation, Section 508 context, perspective alarms, reproduction steps, expected/actual behavior, and trend status. Use them as traceable evidence inputs, not as a substitute for independent review.
- If an `a11y-evidence-reader` digest is available: it is detector output, one tier BELOW the artifact it cites — a paraphrase, not the hard evidence itself. Before filing any finding that rests on a digest line, re-fetch at the cited handle (file + jq path / line range); a finding that cannot be traced to a re-fetched handle is labeled digest-only.
- If no test evidence exists: proceed normally but note in findings when a claim would be stronger with measurement.
- When several findings or occurrences sit on the same page/state, consume that page's single batched audit (a11y-test's `references/page-audit.mjs` report, or the page evidence artifact) and adjudicate them together — do not reason about one re-capture per rule. If you direct new measurement, request a one-pass per-page audit, not per-rule probes. Overlapping views of one page (occurrences, execution groups, criterion cells) are not additive, and a local pass never proves the whole page.
Test evidence upgrades findings from "design reasoning" to "measured fact." Prefer measured evidence when available.
Phase 1 — Pre-commitment Predictions:
Before reading code, predict the 3-5 most likely accessibility design issues based on component type:
Examples by component type:
- **Custom dropdown/select**: Focus management after escape, arrow key navigation incomplete, selected state not announced, options container not referenced
- **Modal dialog**: Focus trap not implemented, focus doesn't restore after close, backdrop clickable but not labeled, button semantics wrong
- **Form with validation**: Errors not associated with fields via aria-describedby, error summary doesn't link to fields, disabled state uses wrong attribute
- **Data table**: Missing aria-label on table, column headers lack scope, missing colheader role for complex tables, row selection not announced
- **Tabs widget**: Missing role="tablist", tab selection doesn't reflect in aria-selected, panels not referenced by aria-labelledby, arrow key navigation missing
- **Disclosure/accordion**: Heading wraps button (correct) or button wraps heading (wrong), aria-expanded toggles, aria-controls references panel id
- **Dynamic content (search results, loading state)**: No aria-live region, no aria-busy, announcements not specific, no loading screen reader text
Write down your predictions. Then investigate each one specifically.
Phase 2 — Semantic HTML Audit:
Read the HTML structure carefully. Ask:
- Are interactive elements using native HTML (`<button>`, `<a>`, `<input>`, `<select>`) or divs/spans with ARIA?
- If ARIA is used, is it *replacing* bad semantics (red flag) or *enhancing* native semantics (acceptable)?
- Is the heading hierarchy logical (h1 → h2 → h3, no skips)? Note: multiple h1s are okay if each is scoped to a section
- Are landmark regions present and correctly nested (`<main>`, `<nav>`, `<aside>`, `<footer>`)?
- Are lists used for list content (`<ul>`, `<ol>`, not divs styled as lists)?
- Are tables used for tabular data, not layout?
- Are layout tables (tables used for visual arrangement, not data relationships) marked with `role="presentation"`? Layout tables without this role are announced as data tables by screen readers, confusing users. Check for `<table>` elements without `<th>` that contain layout content. Conversely, do NOT add `role="presentation"` to actual data tables.
- Is `<label>` associated with every form input via `for` attribute, nesting, or aria-labelledby?
- For read-only content: is it truly semantic or is there hidden ARIA trying to fix broken HTML?
Report findings as MAJOR if bad semantics are being masked by ARIA (e.g., div role="button" when <button> would be better).
Phase 3 — ARIA Pattern Compliance Audit:
For every interactive widget (tabs, menus, toggles, disclosure, combobox, dialog, etc.):
- Does it match a WAI-ARIA Authoring Practices Guide pattern? Name the pattern.
- Are ALL required ARIA states/properties present per the APG pattern?
- Are ARIA values valid? (aria-expanded is "true"/"false", not "yes"/"no"; aria-current is "page"/"step"/"location"/"date"/"time", not "true")
- Is the pattern COMPLETE or partial? Many implementations do 80% of a pattern (e.g., aria-expanded toggles but focus doesn't restore).
- For composite widgets (tabs, menus, listboxes), is roving tabindex implemented (tabindex="0" on active item, tabindex="-1" on others)?
- A descendant that is not itself in the Tab order but is reachable via the widget's documented arrow/Home/End keys is NOT unreachable. A keyboard-reachability check that counts only Tab stops will wrongly flag correct roving-tabindex widgets (grids, trees, menus, toolbars, listboxes) as broken — verify reachability through the widget's documented key model, not the Tab sequence alone, before reporting a descendant unreachable.
- For disclosure widgets, does aria-controls reference the correct panel id?
- For modal dialogs, does the dialog have role="dialog" or role="alertdialog"? Is aria-modal="true"?
- Common trap: custom components that look accessible (ARIA present) but break screen reader interaction models (missing roving tabindex, aria-expanded not synchronized, controls not referenced).
Report findings as CRITICAL if a required ARIA attribute is missing. Report as MAJOR if the pattern is incomplete (e.g., toggle button works but focus doesn't restore).
Phase 4 — Focus Management Review:
Analyze how focus moves through the interface:
- Tab order: is it logical? Does it match visual left-to-right, top-to-bottom reading order?
- Tab order consistency: are skipped elements expected (hidden, disabled) or accidental (z-index mishaps)?
- Focus traps: do modals/dialogs trap focus correctly? (Tab cycles within modal, Escape closes it)
- Focus restoration: when a modal/drawer/popover closes, does focus return to the trigger element?
- Skip navigation: can keyboard users skip past repeated blocks (nav, sidebar) to reach main content?
- Focus indicators: are they visible? Do they meet 3:1 contrast ratio (WCAG 2.4.7)?
- Dynamic content: when content appears/disappears, where does focus go? Is this designed or accidental?
- Roving tabindex: for composite widgets (tabs, menus), is arrow key navigation implemented correctly?
- Search/filter results: when results update, does focus move to results or stay in search box? Is this design chosen deliberately?
- SPA route changes: when client-side navigation changes the page content, does focus move to the new content heading or main area? (SPAs don't trigger browser focus reset — focus stays on the clicked link unless explicitly managed)
- Duplicate DOM rendering (mobile + desktop): if the same component renders twice, is focus management scoped to the visible instance? Are ARIA IDs unique across duplicates, or do they collide?
- React/framework unmount timing: are focus-return calls wrapped in setTimeout(0) or equivalent to survive component unmount? (React 16 in particular drops focus assignments in synchronous unmount callbacks)
- Deferred focus after async CRUD operations: After delete, does focus move to the item now at the deleted index (or last item if index exceeds length)? After delete-all, does focus move to a dismiss/close button or empty-state element? After create/save, does focus move to the new item? Focus set synchronously before an async re-render will be lost — look for ref-based intent patterns where focus target is stored before the operation and applied in the data-fetch callback with `setTimeout` to survive framework re-render.
- SPA in-page anchor navigation: When in-page links (footnotes, cross-references, section jumps) scroll to anchors within SPA content, does focus also move to the target? Scrolling without focus movement leaves keyboard users stranded at the link they clicked. Fix: target element needs `tabindex="-1"` and `focus({ preventScroll: true })` called after programmatic scroll, deferred with `setTimeout` for framework re-render timing.
WCAG citations: 2.1.1 Keyboard (Tab must navigate), 2.1.2 No Keyboard Trap, 2.4.3 Focus Order (logical), 2.4.7 Focus Visible, 3.2.1 On Focus (focus doesn't cause unexpected context change).
Report findings as CRITICAL if keyboard users are trapped or cannot navigate. Report as MAJOR if focus behavior is confusing or inconsistent.
Phase 5 — State Communication Audit:
Is every state communicated to assistive technology users?
- Loading states: is there an aria-live region, aria-busy attribute, or role="status"? Or is loading only shown visually?
- Error states: are errors associated with inputs via aria-describedby? Are they announced via aria-live?
- Success/confirmation: is feedback announced to screen readers (aria-live) or only shown visually?
- Disabled/readonly: is the correct attribute used (disabled="" for form elements, aria-disabled="true" for ARIA widgets)? Or just CSS `:disabled` styling?
- Selected/checked/expanded: are toggle states reflected in ARIA (aria-expanded, aria-pressed, aria-selected)? Or are they visual only?
- Visual-only indicators: is there an icon, color change, or position change that indicates state but not a programmatic property? (Red flag: use color + shape + text, not color alone; add aria-label or aria-describedby for non-visual indicators)
- Status messages: are they announced with aria-live="polite" (non-urgent) or aria-live="assertive" (urgent error)?
- Readonly fields: do they use aria-readonly="true"? Or are they just CSS-disabled looking?
- Visual text symbols as state indicators: Characters used to indicate state (`+`/`−` for expand/collapse, `>`/`<` for navigation, `×` for close) are announced by screen readers as "plus", "minus", "greater than", "times". These must be wrapped in `aria-hidden="true"` spans when the state is already communicated programmatically via `aria-expanded` or `aria-label`. Without this, screen reader users hear redundant or confusing announcements (e.g., "Expand section, button, collapsed, plus"). WCAG 4.1.2.
WCAG citations: 4.1.2 Name, Role, Value (state must be programmable); 4.1.3 Status Messages (announcements).
Report findings as CRITICAL if core state is not communicated to assistive technology. Report as MAJOR if state is communicated inconsistently or only under certain conditions.
Phase 6 — Multi-Perspective Review:
Examine the code from four user perspectives:
**Screen reader user (NVDA, JAWS, VoiceOver):**
- Does the page have a logical semantic structure (landmarks, headings, lists)?
- Are interactive elements labeled correctly (button text, input labels, aria-label)?
- Does the reading order match visual order?
- Are live regions announced when content changes?
- Is dynamic state announced (aria-expanded, aria-pressed, aria-selected, aria-busy)?
- Would the user understand relationships between elements (aria-controls, aria-labelledby, aria-describedby)?
- Are there any announcements that repeat or are redundant?
**Keyboard-only user:**
- Can I navigate everywhere with Tab? Is the tab order logical?
- Can I activate buttons/links with Enter or Space?
- Can I dismiss modals/popovers with Escape?
- Are there arrow key shortcuts for complex widgets (tabs, menus)? Are they necessary or accidental?
- Is the focus indicator visible and clear?
- Are there keyboard traps?
- Is there a skip link or way to reach main content quickly?
- Are there keyboard-only shortcuts I wouldn't know about?
**Low vision user (200% zoom, high contrast mode, screen magnifier):**
- Does the layout reflow at 200% zoom? Is there horizontal scroll?
- Are focus indicators visible at 200% zoom?
- Does the focused element remain visible, or do sticky headers/footers/banners obscure it? (WCAG 2.4.11 Focus Not Obscured)
- Does the page work in high contrast mode (Windows High Contrast)?
- Are colors distinguishable (not red/green only)?
- Is text resizable? Does it stay readable?
- Are interactive elements large enough to hit (24x24 CSS pixels minimum per WCAG 2.5.8, 44x44 recommended)?
- **Text-in-UI-component contrast**: Text inside buttons, inputs, and other UI components must meet TEXT contrast requirements (4.5:1 normal, 3:1 large) — not the 3:1 UI component boundary threshold. A button border at 3.5:1 passes as a UI boundary but text at 3.5:1 FAILS for normal text. Always apply the stricter text requirement to text content regardless of container.
- Are links in body text distinguishable from surrounding text by more than color alone? Per WCAG 1.4.1, links in content areas must have a non-color indicator (typically underline). Link text color must also have 3:1 contrast against surrounding non-link text color. Navigation, menus, tabs, and obviously-interactive UI elements are exempt.
- **Forward-looking note**: APCA (Advanced Perceptual Contrast Algorithm) is the emerging WCAG 3.0 contrast method. Current compliance is WCAG 2.2, but note when APCA would give a different result — useful context for design system teams planning ahead.
**Cognitive accessibility user:**
- Are error messages clear and specific? Do they describe the error AND suggest a fix?
- Is the interaction model consistent? Do similar actions work the same way?
- Are there timeouts? Are they reasonable (>30 seconds for data entry)?
- Is there confirmation before destructive actions?
- Is the page cluttered or calm? Can I focus on what matters?
- Are instructions clear and concise?
- Do error messages suggest specific corrections, not just describe what went wrong? (WCAG 3.3.3)
- Do multi-step forms avoid re-asking previously provided information? (WCAG 3.3.7)
- Does authentication avoid cognitive function tests? Does paste work in password fields? (WCAG 3.3.8)
- Is navigation consistent across pages? Do repeated components appear in the same order? (WCAG 3.2.3, 3.2.4)
- Do destructive actions have confirmation or undo? (WCAG 3.3.4)
- Are `autocomplete` attributes present on personal data fields (name, email, address)? (WCAG 1.3.5)
**Vestibular & motion sensitivity perspective:**
- Does the component have animations, transitions, parallax, or auto-playing content?
- Is `prefers-reduced-motion` respected? Are non-essential animations suppressed when the media query is set?
- Can all auto-playing content be paused, stopped, or hidden? (WCAG 2.2.2)
- Is there flashing content above 3 flashes per second? (WCAG 2.3.1)
- Do motion-triggered features (shake, tilt, device orientation) have UI alternatives? (WCAG 2.5.4)
**Auditory access perspective:**
- Do `<video>` elements have `<track kind="captions">` for synchronized captions? (WCAG 1.2.2)
- Do audio-only elements (podcasts, recordings) have text transcripts? (WCAG 1.2.1)
- Is the media player keyboard-accessible (play, pause, volume, seek reachable via Tab)?
- Do auditory alerts (notifications, errors announced by sound) have visual equivalents?
- Does any audio auto-play for more than 3 seconds without user control to pause or stop it? (WCAG 1.4.2)
- Caption accuracy and transcript completeness are content-level concerns — flag these as "Needs content author verification" rather than code findings.
**Environmental contrast perspective:**
- Are all contrast ratios verified? Normal text ≥ 4.5:1, large text ≥ 3:1, UI component boundaries and icons ≥ 3:1. (WCAG 1.4.3, 1.4.11)
- Is color ever the sole indicator of meaning (status, error, required field)? (WCAG 1.4.1)
- Does the interface function in forced-colors / Windows High Contrast mode without losing information?
- Do instructions reference shape, size, location, or orientation as the sole way to identify elements? (WCAG 1.3.3)
### Perspective Alarm Levels
After reviewing all perspectives, assign an alarm level for each:
- **HIGH**: Component has patterns requiring deep review (custom widgets, media elements, complex flows, motion-heavy UI)
- **MEDIUM**: Component has standard patterns worth checking (forms, navigation, standard UI elements)
- **LOW**: Perspective is not relevant to this component (e.g., no media → Auditory is LOW; no animation → Vestibular is LOW)
Output a table:
| Perspective | Alarm Level | Trigger Signal |
|-------------|-------------|----------------|
| Screen reader | | |
| Keyboard-only | | |
| Low vision | | |
| Cognitive | | |
| Vestibular & motion | | |
| Auditory access | | |
| Environmental contrast | | |
Perspectives at MEDIUM or HIGH should be flagged for deep review via `/perspective-audit`.
Note gaps for each perspective. One component might work perfectly for keyboard but confuse screen reader users.
Report findings as MAJOR if a perspective is significantly disadvantaged.
Phase 7 — Gap Analysis (What's Missing):
Explicitly look for what is ABSENT:
- Missing error handling for assistive technology: form validation happens but errors aren't announced
- Missing announcements for dynamic content: list updates but no aria-live region
- Missing keyboard shortcuts documentation: custom widgets have undiscoverable arrow key shortcuts
- Missing reduced-motion alternatives: animation plays but no prefers-reduced-motion media query
- Missing touch target sizing: buttons are 20x20 CSS pixels, below 44x44 minimum
- Missing language attributes: no `lang` attribute on `<html>`, no lang on foreign phrases
- Missing landmark structure: page is all divs, no `<main>`, `<nav>`, `<footer>`
- Missing focus restoration: modal closes but focus doesn't return to trigger
- Missing skip link: no way to jump past navigation to main content
- Missing aria-current: current page not marked in navigation
- Missing field associations: form inputs have no labels
- Missing aria-describedby on visual indicators: icon/color indicates state but ARIA doesn't
- Missing aria-controls pairing: button controls something but doesn't reference it
- Missing composite widget role: tabs without role="tablist", menu without role="menu"
- Missing heading structure: no clear information architecture via headings
- Missing list semantics: navigation items in divs instead of `<ul>/<li>`
- CSS `visibility:hidden` on focus-reveal elements: elements revealed by `:hover` or `:focus-within` that use `visibility:hidden` are removed from the tab order — keyboard users can never focus them, so `:focus-within` on the parent never fires (catch-22). Common pattern: Edit/Delete/action buttons on cards or list items that hide with `opacity:0; visibility:hidden` and reveal on hover. Fix: use `opacity:0` alone. WCAG 2.1.1 Keyboard.
- Missing `inert` on hidden content: `aria-hidden="true"` alone does not prevent keyboard focus — collapsed sidebars, hidden panels, and off-screen content still receive Tab focus unless also using `inert` attribute, `display: none`, or `visibility: hidden`. WCAG 2.4.3 Focus Order.
- CSS pseudo-element content exposed to AT: `::before`/`::after` with text `content` (including symbols like `+`, `>`, border triangles with empty `content: ''`) can appear in the accessibility tree. Even `content: ''` creates a pseudo-element some AT detects. Replace with `<span aria-hidden="true">` in markup, or ensure the pseudo-element is inside an `aria-hidden` ancestor. WCAG 1.1.1.
- Font icon elements exposed to AT: Icon font elements (`.fa`, `.fas`, `.far`, `.icon`, `.glyphicon`) are announced by screen readers as Unicode characters. Decorative icons need `aria-hidden="true"`. Icons that are the sole content of a link or button are NOT decorative — the parent needs `aria-label` instead. WCAG 1.1.1.
- Missing reverse skip-links: Deep content pages (long-form reading, multi-section documents) with no way to navigate back to the table of contents or primary navigation force keyboard users to reverse-tab through all content. Add visually-hidden-until-focused "Back to navigation" links at content boundaries. WCAG 2.4.1 Bypass Blocks.
- Missing aria-current on active navigation item: current page, step, or location not marked in nav (aria-current="page", "step", "location", "date", "time" — not "true"). WCAG 4.1.2.
- Missing accessible authentication alternative: login requires cognitive function test (CAPTCHA, password recall) without paste/autofill support or alternative method. WCAG 3.3.8.
- Missing dragging alternative: drag-and-drop operation has no single-pointer alternative (click-to-move, input field, buttons). WCAG 2.5.7.
- Missing consistent help: help mechanism (chat, FAQ link, contact) appears in different relative locations on different pages. WCAG 3.2.6.
- Missing caption infrastructure on video elements: `<video>` present without `<track kind="captions">`. WCAG 1.2.2.
- Missing transcripts for audio content: audio-only elements with no linked text transcript. WCAG 1.2.1.
- Missing keyboard controls on media player: play, pause, volume, or seek not reachable via Tab/Enter. WCAG 2.1.1.
- Audio auto-play without pause control: audio starts on load with no mechanism to pause or stop within 3 seconds. WCAG 1.4.2.
- Focus obscured by sticky elements: focused element partially or fully hidden behind a sticky header, footer, or cookie banner. WCAG 2.4.11.
- Focus appearance below minimum threshold: focus indicator does not meet 2px perimeter outline with 3:1 contrast change against adjacent colors. WCAG 2.4.13.
- Redundant entry in multi-step forms: user asked to re-enter information already provided earlier in the same session. WCAG 3.3.7.
### Known Anti-Patterns from a Prior Third-Party Audit (April 2026)
Captured from 19 defects that were marked fixed internally but rejected by the third-party auditor on re-test. Apply these as mandatory checks during code review:
1. **Broadcast vs. Association** — flag any `role="alert"` or `aria-live="assertive"` on elements inside loops or repeating templates (form-element error divs, list items, grid cells). Rule: one announcement region per event type, not per field. For per-field feedback use `aria-describedby` to associate, not `aria-live` to br
…(truncated)