# A11yaudit

> Forensic accessibility audit v1 (Gestalt-Popper). 21-phase deep analysis of every accessibility surface: WCAG 2.1 AA compliance, keyboard navigation (every interactive element), screen reader testing, ARIA labels/roles/states, color contrast (4.5:1 AA, 3:1 large text), focus management, skip navigation, form labels, error announcements, alt text, heading hierarchy, landmark regions, touch targets (44px), motion/animation preferences, cognitive load, reading level, plus verdict, fix plan, fix execution, re-audit, and integration smoke gate. Score /320. Preamble v1.0 compliant. Audit -> Plan -> Fix -> Re-audit. Use when user says "/a11yaudit", "accessibility audit", "wcag audit", "a11y check", "keyboard navigation test", "screen reader test", "color contrast check".

- Skill: `agentik-os/a11yaudit` (Agent Skill)
- Install (CLI): `npx skillmds@latest add agentik-os/a11yaudit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/agentik-os/a11yaudit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: agentik-os (https://skillmd.com/u/agentik-os)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/agentik-os/a11yaudit

---



<!-- AUDIT-META-V2-INJECTED -->

> ## ⚠️ MANDATORY FIRST STEP — READ THE V2 META-PROTOCOL
>
> **Before doing ANYTHING else**, Read `../_shared/audit-meta-protocol-v2.md`.
>
> That file overrides any conflicting guidance below for these five aspects:
> 1. Required CLI inputs (`--user-need`, `--hinge` are MANDATORY since 2026-05-08)
> 2. Required JSON output schema (v2: score + confidence + falsifiable_tests + user_need_match + hinge_findings)
> 3. Popper falsification — every PASS must cite ≥3 concrete commands run with actual output
> 4. Confidence calibration — `high` requires direct verification of every claim
> 5. Banned shortcut phrases — `looks correct`, `should be fine`, `appears to work` = automatic FAIL
>
> If `--user-need` or `--hinge` is missing from your invocation, refuse to run and write
> `{"score":0,"confidence":"low","error":"missing v2 inputs","request_redispatch":true}`.
>
> The legacy v1 schema (`{"score":100,"skill_used":"<name>"}`) is accepted with a warning until 2026-06-01,
> then removed. Always emit v2 going forward.
>
> Model context: this audit runs on Opus 4.7 with max effort. There is no time pressure.
> Run every test you claim to have run. Cite verbatim outputs. No exceptions.

---

# /a11yaudit v1 — Forensic Accessibility Audit (Gestalt-Popper)

> *"The other audits ask 'does it work?' I ask 'can EVERYONE use it?'"*

---

## DOCTRINE

You are not an accessibility checker. You are an **accessibility forensic pathologist**. The running application is your patient — possibly excluding millions of users, definitely violating legal requirements, pretending to be accessible because nobody tested with a keyboard. Your job is to find every barrier, every missing label, every trapped focus while automated tools say "0 violations."

**The 5 Laws of Accessibility Forensics (Gestalt-Popper Synthesis):**
1. **If it renders, it's still guilty.** A page that looks perfect visually may be completely invisible to a screen reader. Accessibility bugs are silent exclusions — the victims are users who never complained because they couldn't even reach the feedback form.
2. **Automated tools lie (Popper).** axe-core catching 30% of issues doesn't mean 70% remain — it means 70% are INVISIBLE to automation. FALSIFY every "0 violations" report with manual keyboard testing, screen reader flows, and real-device magnification.
3. **Every missing label is a locked door.** That unlabeled button. That image without alt text. That form with no error announcement. Each is a barrier for someone who has no alternative path.
4. **Clarity before scanning (Gestalt).** Before launching any tool, UNDERSTAND the product. Read VISION.md, CLAUDE.md, README. Identify the **HINGE FLOW** — the primary user journey from landing to goal completion. Test the hinge flow with 10x depth.
5. **"Works for me" is not accessible (Popper).** "I can tab through it" means nothing if focus isn't visible. FALSIFY every "it works" claim with screen reader, keyboard-only, zoom 200%, reduced motion, and high contrast mode.

**Gestalt Hinge Flow:** Before Phase 1, identify THE user journey that defines the product experience. Signup -> Dashboard -> Primary Action. THIS flow gets every phase at maximum depth.

**Popper Accessibility Falsification Categories:**
- **VISUAL vs SEMANTIC** — Looks like a button, but is a div with an onClick
- **MOUSE vs KEYBOARD** — Works with click, impossible with Tab/Enter
- **SIGHTED vs SCREEN READER** — Visible text, but no programmatic association
- **DESKTOP vs MOBILE** — Accessible on desktop, touch targets too small on mobile
- **DEFAULT vs PREFERENCE** — Animations play, even with prefers-reduced-motion

---

## SCOPE DETECTION (automatic)

```
EXAMPLES:
  "/a11yaudit"
  -> Full 16-phase pipeline. Discover all pages, test everything.

  "/a11yaudit the signup flow"
  -> TARGETED: only signup/auth pages
  -> All phases scoped to signup routes

  "/a11yaudit keyboard"
  -> KEYBOARD-FOCUSED: tab order, focus traps, interactive elements

  "/a11yaudit forms"
  -> FORM-FOCUSED: labels, errors, validation, required fields

  "/a11yaudit contrast"
  -> CONTRAST-FOCUSED: color ratios, text on backgrounds, UI components
```

---

## OUTPUT CONTRACT

```
audits/.a11yaudit/
|-- session.log
|-- discovery/
|   |-- pages.json              # All discovered routes/pages
|   |-- interactive-elements.json # All buttons, links, inputs, etc.
|   |-- aria-inventory.json     # All ARIA attributes found
|   |-- heading-map.json        # Heading hierarchy per page
|-- reports/
|   |-- wcag-compliance.md          # Phase 1
|   |-- keyboard-navigation.md     # Phase 2
|   |-- screen-reader.md           # Phase 3
|   |-- aria-audit.md              # Phase 4
|   |-- color-contrast.md          # Phase 5
|   |-- focus-management.md        # Phase 6
|   |-- skip-navigation.md         # Phase 7
|   |-- form-labels.md             # Phase 8
|   |-- error-announcements.md     # Phase 9
|   |-- alt-text.md                # Phase 10
|   |-- heading-hierarchy.md       # Phase 11
|   |-- landmark-regions.md        # Phase 12
|   |-- touch-targets.md           # Phase 13
|   |-- motion-preferences.md      # Phase 14
|   |-- cognitive-load.md          # Phase 15
|   |-- reading-level.md           # Phase 16
|-- verdict.json
|-- verdict.md
|-- fix-plan.json
|-- fix-plan.md
|-- progress.json
|-- fix-log.md
```

---

## PHASE 0 — PROGRAMMATIC GATHER (HYBRID, runs FIRST, before all other phases)

> **NEW (2026-05-08, hybrid framework):** before any LLM analysis, programmatic
> tools gather every machine-checkable finding deterministically. The LLM then
> READS the resulting JSON instead of hand-grepping the codebase. Freed token
> budget is REINVESTED in deeper Popper falsification, hinge-point synthesis,
> user-need verification, and edge-case hunting.

### 0.1 Run the gather script (mandatory, FIRST step)

```bash
~/.omega/lib/audit-runner.sh a11y "$PROJECT_PATH" \
  --files="$FILES_MODIFIED" \
  --url="$URL" \
  --user-need="$USER_NEED_QUOTE" \
  --hinge="$HINGE_POINT" \
  --ticket="$TICKET_ID"
```

This invokes `~/.omega/lib/audit-gather/a11y.sh` which runs:
axe-core, pa11y, and Lighthouse a11y category — runs in headless Chromium against a live URL

Output is written to:

```
$PROJECT_PATH/audits/.a11yaudit/
├── raw/                    # raw tool outputs (JSON / text per tool)
└── evidence-summary.json   # normalized findings, single source of truth for the LLM
```

When run inside a Linear-fix mission (`--ticket=ID`), the artifacts move to
`$PROJECT_PATH/audits/.linear-fix/<ID>/.a11yaudit/` so multiple audits on the same
ticket can cross-reference each other (see 0.5).

### 0.2 evidence-summary.json schema

```jsonc
{
  "audit": "a11y",
  "tools_run": ["..."],
  "tools_skipped": [{"tool": "...", "reason": "..."}],
  "findings_total": 514,
  "findings_by_severity": {"critical": 2, "high": 17, "medium": 89, "low": 406, "info": 0},
  "findings": [
    {
      "tool": "...",
      "severity": "critical|high|medium|low|info",
      "location": "file:line[:col]",
      "rule": "...",
      "message": "...",
      "suggested_fix": "...",
      "cross_tool_confirmed": false
    }
  ],
  "metrics": { /* tool-specific quantitative data */ },
  "evidence_index": { /* paths to raw/ files for drill-down */ }
}
```

### 0.3 What you do AFTER the gather (this replaces hand-greps)

You now consume `evidence-summary.json` programmatically. You MUST:

1. **Read `evidence-summary.json` in full.** This is your evidence base.
2. **Read 3-5 critical files only** — the ones flagged as load-bearing in
   `~/.omega/state/hinge-points-<ticket>.json` (or computed via
   `${OMEGA_DIR:-$HOME/.omega}/skills/audits/_shared/hinge-analyzer.sh` if no ticket).
3. **DO NOT manually grep the codebase for what the gather already covered.**
   The tools have already exhaustively scanned every file. Re-running grep
   wastes tokens and produces the same evidence.
4. **DO read additional files** when (a) a finding's context is unclear from
   message+location, (b) you need to verify a Popper falsification, or
   (c) you suspect a missed edge case (Phase 2.4 below).

### 0.4 Banned operations after Phase 0

These are now forbidden because the gather already did them. If you catch
yourself about to run one, STOP and read `evidence-summary.json` first:

- ❌ `grep -rn "TODO" .` (the gather scanned for it)
- ❌ `find . -name "*.ts" | xargs wc -l` (the gather has size metrics)
- ❌ `npm audit` / `pip-audit` (the gather ran them — read the JSON)
- ❌ `eslint .` / `tsc --noEmit` / `lighthouse <url>` (already in raw/)
- ❌ Generic "let me check every file" loops (the gather's job, not yours)

You MAY still:
- ✅ Read SPECIFIC files cited in findings (verify the issue)
- ✅ Run a SPECIFIC `grep` to falsify a finding (Popper test, see Phase 2.1)
- ✅ Run a SPECIFIC tool the gather couldn't (e.g. dynamic Playwright probe
  for a flow scenario the static gather can't model)

### 0.5 Cross-audit synthesis (read sibling evidence-summary.json files)

If this audit runs as part of a Linear-fix mission, sibling audits' summaries
are at `$PROJECT_PATH/audits/.linear-fix/<TICKET>/.<other-audit-id>/evidence-summary.json`.
Read them. Use them.

Examples of high-value cross-audit findings:
- **codeaudit + secaudit** flag the same `auth.ts` line → confidence escalation,
  the file is BOTH a code-quality risk AND a security risk.
- **perfaudit + a11yaudit** on the same image → joint fix opportunity (lazy-load
  + `alt` attribute in one change).
- **apiaudit + dataaudit** on the same endpoint+table pair → contract drift
  between the API surface and the schema.
- **debugaudit + flowaudit** report the same broken page → user-flow blocker.

When you find such a confluence, mark the finding `cross_audit_confirmed: true`
in your `verdict.json` and bump severity by one level.

---

## PHASE 0: RECONNAISSANCE

> *"Know who your users are before testing what they can't do."*

```
1. PROJECT DISCOVERY
   -> Read CLAUDE.md, README, package.json
   -> Identify: framework, component library, CSS approach
   -> Find: prod URL, dev URL, target audience, known a11y requirements

2. PAGE/ROUTE DISCOVERY
   -> Scan all routes and interactive pages
   -> Build complete sitemap with interaction types per page
   -> Identify high-frequency user journeys (hinge flows)

3. INTERACTIVE ELEMENT INVENTORY
   -> List all buttons, links, inputs, selects, custom widgets
   -> List all modals, dialogs, dropdowns, tooltips, tabs
   -> List all media (images, videos, audio)
   -> List all dynamic content (live regions, notifications, loading states)

4. ACCESSIBILITY BASELINE
   -> Run axe-core automated scan on all pages
   -> Document existing ARIA usage patterns
   -> Note any accessibility statement or VPAT
   -> This becomes the "before" for comparison
```

---

## PHASE 1: WCAG 2.1 AA COMPLIANCE AUDIT

> *"The law says AA. Most sites fail Level A. That's not a gap — it's a chasm."*

```
FOR EVERY discoverable page:

1. PERCEIVABLE (WCAG 1.x)
   -> 1.1.1 Non-text content: every image, icon, chart has text alternative
   -> 1.2.x Time-based media: captions, audio descriptions, transcripts
   -> 1.3.1 Info and relationships: structure conveyed programmatically
   -> 1.3.2 Meaningful sequence: reading order matches visual order
   -> 1.3.3 Sensory characteristics: not "click the red button"
   -> 1.3.4 Orientation: works in both portrait and landscape
   -> 1.3.5 Input purpose: autocomplete attributes on personal data fields
   -> 1.4.x Visual: contrast, resize, images of text, reflow, spacing

2. OPERABLE (WCAG 2.x)
   -> 2.1.1 Keyboard: all functionality available via keyboard
   -> 2.1.2 No keyboard trap: focus can always escape
   -> 2.1.4 Character key shortcuts: can be turned off/remapped
   -> 2.2.x Timing: adjustable, pause/stop, no interruptions
   -> 2.3.1 Three flashes: no content flashes > 3 times/second
   -> 2.4.x Navigation: skip links, page titles, focus order, link purpose
   -> 2.5.x Input modalities: pointer gestures, pointer cancellation, label in name, motion actuation

3. UNDERSTANDABLE (WCAG 3.x)
   -> 3.1.1 Language of page: lang attribute present
   -> 3.1.2 Language of parts: foreign phrases marked
   -> 3.2.x Predictable: on focus, on input, consistent navigation, consistent identification
   -> 3.3.x Input assistance: error identification, labels, error suggestion, error prevention

4. ROBUST (WCAG 4.x)
   -> 4.1.1 Parsing: valid HTML (no duplicate IDs)
   -> 4.1.2 Name, role, value: custom components expose correct semantics
   -> 4.1.3 Status messages: conveyed without focus change

FALSIFY: Run automated scan AND manual check on every criterion. If automated says "pass," manually verify with screen reader. 30% of real failures pass automated checks.
```

---

## PHASE 2: KEYBOARD NAVIGATION (THE HINGE)

> *"If you can't use it with a keyboard, it doesn't work. Period."*

```
THIS IS THE HINGE PHASE. Score this with 2x weight.

FOR EVERY interactive element on EVERY page:

1. TAB ORDER
   -> Tab through entire page start to finish
   -> Is order logical? (left-to-right, top-to-bottom, matching visual flow)
   -> Any elements skipped that shouldn't be?
   -> Any non-interactive elements receiving focus?
   -> tabindex > 0 used anywhere? (anti-pattern)

2. FOCUS VISIBILITY
   -> Is focus indicator ALWAYS visible?
   -> Custom focus styles meet 3:1 contrast ratio?
   -> Focus indicator at least 2px thick?
   -> No CSS outline:none without replacement?
   -> Focus visible in all color modes (light/dark/high contrast)?

3. INTERACTIVE ELEMENT ACTIVATION
   -> Every button activatable with Enter AND Space?
   -> Every link activatable with Enter?
   -> Custom dropdowns operable with arrow keys?
   -> Custom tabs operable with arrow keys?
   -> Escape closes modals/dropdowns?

4. KEYBOARD TRAPS
   -> Can focus leave every modal/dialog?
   -> Can focus leave every dropdown/menu?
   -> Date pickers keyboard navigable?
   -> Custom widgets allow focus escape?
   -> No infinite tab loops?

5. COMPLETE FLOW TEST
   -> Complete primary user journey keyboard-only
   -> Complete secondary user journeys keyboard-only
   -> Time keyboard flow vs mouse flow (target: < 2x mouse time)
   -> Document every keyboard failure point

FALSIFY: Disconnect mouse. Complete entire hinge flow. If you get stuck at ANY point, that's a CRITICAL failure.
```

---

## PHASE 3: SCREEN READER TESTING

> *"If the screen reader can't describe it, it doesn't exist."*

```
1. PAGE STRUCTURE ANNOUNCEMENT
   -> Page title announced on navigation
   -> Headings announced in correct hierarchy
   -> Landmarks announced (main, nav, aside, footer)
   -> List items announced with count
   -> Table structure announced (rows, columns, headers)

2. INTERACTIVE ELEMENT ANNOUNCEMENTS
   -> Buttons: name + role announced
   -> Links: destination or purpose announced
   -> Inputs: label + type + state announced
   -> Checkboxes/radios: label + checked state
   -> Custom components: correct role announced

3. DYNAMIC CONTENT
   -> Notifications announced via live regions
   -> Loading states announced
   -> Error messages announced immediately
   -> Content updates in live regions announced
   -> Form submission results announced

4. NAVIGATION EFFICIENCY
   -> Can jump to main content (skip nav)
   -> Can navigate by headings
   -> Can navigate by landmarks
   -> Can navigate by form elements
   -> Reading order matches visual layout

5. VIRTUAL BUFFER TESTING
   -> All content reachable in browse mode
   -> Form mode switches correctly
   -> Application mode used appropriately
   -> No content hidden from screen reader that should be visible
```

---

## PHASE 4: ARIA LABELS, ROLES, AND STATES

> *"ARIA is the bridge between what's visual and what's programmatic. Broken bridges are worse than no bridge."*

```
1. ARIA ROLE AUDIT
   -> Custom widgets have correct roles (dialog, menu, tab, etc.)
   -> No redundant roles on native elements (role="button" on <button>)
   -> Abstract roles not used (widget, roletype, structure)
   -> Document/application roles used correctly (not on <body>)
   -> Composite roles have required children (menu -> menuitem)

2. ARIA LABEL AUDIT
   -> aria-label on icon-only buttons
   -> aria-label on ambiguous links ("Read more" -> "Read more about X")
   -> aria-labelledby references existing, visible elements
   -> aria-describedby for supplementary info (not primary label)
   -> Labels match visible text (label-in-name)

3. ARIA STATE AUDIT
   -> aria-expanded on collapsible sections
   -> aria-selected on tabs and options
   -> aria-checked on custom checkboxes
   -> aria-current on navigation (current page/step)
   -> aria-disabled vs HTML disabled (correct usage)
   -> aria-hidden not hiding visible content
   -> aria-invalid on invalid form fields

4. ARIA RELATIONSHIP AUDIT
   -> aria-controls connects trigger to target
   -> aria-owns for DOM reordering
   -> aria-flowto for non-linear reading order
   -> aria-errormessage on fields with errors
   -> Form groups with aria-labelledby or fieldset/legend

5. FIRST RULE OF ARIA
   -> If native HTML can do it, ARIA is NOT needed
   -> Count ARIA overrides of native semantics -> each is a risk
   -> Verify: removing all ARIA, does native HTML convey the same info?
```

---

## PHASE 5: COLOR CONTRAST

> *"4.5:1 isn't a suggestion. It's the minimum for someone with 20/40 vision."*

```
1. TEXT CONTRAST (AA minimum)
   -> Normal text (< 18pt / < 14pt bold): 4.5:1 minimum
   -> Large text (>= 18pt / >= 14pt bold): 3:1 minimum
   -> Check EVERY text color against EVERY background it appears on
   -> Include text over images/gradients (worst-case pixel)
   -> Include placeholder text (often fails)

2. UI COMPONENT CONTRAST
   -> Form input borders: 3:1 against background
   -> Button boundaries: 3:1 against surrounding area
   -> Focus indicators: 3:1 against adjacent colors
   -> Icons that convey meaning: 3:1 minimum
   -> Charts/graphs data elements: 3:1 between adjacent elements

3. STATE CONTRAST
   -> Hover state distinguishable (not just color change)
   -> Active/pressed state distinguishable
   -> Disabled state still readable (though can be lower contrast)
   -> Selected/unselected in lists, tabs, toggles
   -> Error/success/warning not conveyed by color alone

4. COLOR-ONLY INFORMATION
   -> Links distinguishable from text without color alone (underline, weight)
   -> Required fields indicated without color alone (asterisk, text)
   -> Error messages have icon/text, not just red
   -> Charts have patterns/labels, not just colors
   -> Status indicators have text/icon, not just color

5. DARK MODE / HIGH CONTRAST
   -> All contrast ratios hold in dark mode
   -> All contrast ratios hold in Windows High Contrast
   -> Forced-colors media query supported
   -> No information lost in high contrast mode

FALSIFY: Use grayscale filter on entire page. If ANY information is lost, contrast-only communication is present.
```

---

## PHASE 6: FOCUS MANAGEMENT

> *"Focus is the keyboard user's cursor. Lose it and you lose the user."*

```
1. FOCUS ON PAGE LOAD
   -> Focus starts at logical position (skip link or main content)
   -> No auto-focus on non-essential elements
   -> Auto-focus on primary input in forms (when appropriate)

2. FOCUS ON ROUTE CHANGE (SPA)
   -> Focus moves to new content on navigation
   -> Page title updates on route change
   -> Focus not lost in void after navigation
   -> Back button restores focus position

3. MODAL/DIALOG FOCUS
   -> Focus moves to dialog on open
   -> Focus trapped within dialog while open
   -> Focus returns to trigger on close
   -> Escape key closes dialog and returns focus

4. DYNAMIC CONTENT FOCUS
   -> New content announced via aria-live (not focus move)
   -> Deleted content: focus moves to logical next element
   -> Expanded content: focus moves into new content OR stays
   -> Loading complete: focus moves to result or stays in context

5. FOCUS MANAGEMENT ANTI-PATTERNS
   -> Focus jumping unexpectedly
   -> Focus lost (goes to <body>) after interaction
   -> Focus moving on hover (not on activation)
   -> Focus moving backward in document order
   -> Multiple focus indicators visible simultaneously
```

---

## PHASE 7: SKIP NAVIGATION

> *"50 Tab presses before reaching content. That's not navigation, it's punishment."*

```
1. SKIP LINK PRESENCE
   -> "Skip to main content" link present
   -> Skip link is first focusable element
   -> Skip link target is correct (#main-content or similar)
   -> Skip link visible on focus (can be visually hidden otherwise)

2. SKIP LINK FUNCTIONALITY
   -> Activating skip link moves focus past navigation
   -> Focus target is valid landmark or heading
   -> Content after skip is immediately accessible
   -> Works with both keyboard and screen reader

3. ADDITIONAL SKIP MECHANISMS
   -> Skip to search (if search is a primary feature)
   -> Skip navigation in repeated blocks (sidebar, footer nav)
   -> Table of contents for long content pages
   -> Section navigation for dashboard pages

4. LANDMARK NAVIGATION (alternative to skip links)
   -> <header>, <nav>, <main>, <aside>, <footer> present
   -> Multiple <nav> elements distinguished with aria-label
   -> Screen reader can jump between landmarks
   -> All content within a landmark region
```

---

## PHASE 8: FORM LABELS

> *"An unlabeled input is a guessing game. Users don't play guessing games — they leave."*

```
1. LABEL ASSOCIATION
   -> Every input has a visible <label> with matching for/id
   -> Every input has programmatic label (label, aria-label, aria-labelledby)
   -> Placeholder text is NOT the only label
   -> Group labels via <fieldset>/<legend> for related inputs

2. LABEL QUALITY
   -> Labels are descriptive ("Email address" not "Input 1")
   -> Labels indicate format requirements ("Date (MM/DD/YYYY)")
   -> Required fields marked visually AND programmatically (aria-required)
   -> Character limits communicated in label or description

3. INPUT TYPES
   -> Correct input types (email, tel, url, number, date)
   -> autocomplete attributes on personal data fields
   -> inputmode set for mobile keyboards
   -> Pattern attributes with descriptive title

4. FORM INSTRUCTIONS
   -> Instructions appear BEFORE the form (not only after)
   -> Required field legend appears BEFORE required fields
   -> Complex forms have introductory text explaining sections
   -> Multi-step forms show progress and current step

5. SELECT AND CUSTOM INPUTS
   -> <select> has visible label
   -> Custom dropdowns have role="combobox" or "listbox"
   -> Custom date pickers have accessible label and instructions
   -> File upload has label indicating accepted formats
```

---

## PHASE 9: ERROR ANNOUNCEMENTS

> *"A sighted user sees red. A screen reader user hears... nothing. That's the bug."*

```
1. INLINE VALIDATION ERRORS
   -> Error messages appear near the invalid field
   -> Error messages are programmatically associated (aria-describedby or aria-errormessage)
   -> Error messages announce via screen reader without page reload
   -> Error messages describe the problem AND how to fix it
   -> Field is marked with aria-invalid="true"

2. FORM SUBMISSION ERRORS
   -> Error summary appears at top of form
   -> Error summary links to specific fields
   -> Focus moves to error summary on submission failure
   -> Error count announced ("3 errors found")
   -> All errors listed with links to fields

3. SUCCESS ANNOUNCEMENTS
   -> Form success confirmed via aria-live or focus management
   -> Success message is programmatically distinct from error
   -> Navigation after success is communicated
   -> No silent redirects after form submission

4. DYNAMIC VALIDATION
   -> Real-time validation announced via aria-live="polite"
   -> Not too chatty (wait for user to finish typing)
   -> Character count announced periodically, not on every keystroke
   -> Password strength meter accessible

5. ERROR PREVENTION
   -> Destructive actions require confirmation
   -> Confirmation dialogs are accessible
   -> Undo available and announced
   -> Data loss warnings on navigation away from unsaved form
```

---

## PHASE 10: ALT TEXT

> *"An image without alt text is a mystery box. And not the fun kind."*

```
1. INFORMATIVE IMAGES
   -> Every informative image has descriptive alt text
   -> Alt text describes the image content AND purpose
   -> Alt text length appropriate (< 150 chars for most)
   -> Complex images (charts, diagrams) have long description

2. DECORATIVE IMAGES
   -> Decorative images have alt="" (empty, not missing)
   -> Background images don't convey information
   -> Decorative icons have aria-hidden="true"
   -> Purely visual separators/borders have no alt

3. FUNCTIONAL IMAGES
   -> Image links have alt describing destination
   -> Image buttons have alt describing action
   -> Logo images have alt with company/site name
   -> Social media icons have alt with platform name + action

4. COMPLEX IMAGES
   -> Charts have data table alternative
   -> Infographics have full text description
   -> Maps have text address/directions
   -> Screenshots have text description of shown content

5. SVG ACCESSIBILITY
   -> <svg> has role="img" and aria-label
   -> Or <svg> has <title> and aria-labelledby
   -> Decorative SVGs have aria-hidden="true"
   -> Inline SVGs not announcing path data
```

---

## PHASE 11: HEADING HIERARCHY

> *"Headings are the table of contents for screen reader users. A broken TOC means no navigation."*

```
1. HEADING STRUCTURE
   -> Exactly one <h1> per page
   -> Headings don't skip levels (h1 -> h3 without h2)
   -> Heading levels reflect content hierarchy
   -> All sections introduced by a heading

2. HEADING CONTENT
   -> Headings are descriptive of section content
   -> Headings are unique within page (or clearly contextual)
   -> No empty headings (styled div pretending to be heading)
   -> No non-heading text styled to look like a heading

3. HEADING NAVIGATION
   -> Screen reader heading navigation reveals page structure
   -> Can understand page layout from headings alone
   -> Card titles use appropriate heading level
   -> Dynamic content sections have headings

4. HEADING IN COMPONENTS
   -> Modal titles are headings (h2 typically)
   -> Card components use headings for titles
   -> Accordion panels use headings for triggers
   -> Tab panel content starts with heading
```

---

## PHASE 12: LANDMARK REGIONS

> *"Landmarks are the map. Without them, screen reader users wander."*

```
1. REQUIRED LANDMARKS
   -> <main> present (exactly one)
   -> <nav> present for navigation
   -> <header> for site header (banner role)
   -> <footer> for site footer (contentinfo role)

2. LANDMARK LABELING
   -> Multiple <nav> elements have unique aria-label
   -> <aside> elements have aria-label
   -> <section> elements with aria-label are regions
   -> Landmarks have human-readable labels

3. LANDMARK COVERAGE
   -> All visible content within a landmark
   -> No orphaned content between landmarks
   -> Complementary content in <aside>
   -> Search functionality in search landmark

4. LANDMARK NESTING
   -> No invalid nesting (banner inside banner)
   -> Complementary not nested in main (unless intentional)
   -> Navigation can be inside header
   -> Form landmark for significant forms
```

---

## PHASE 13: TOUCH TARGETS

> *"44px. That's Apple's minimum. Google says 48px. Below that, it's a game of Minesweeper."*

```
1. MINIMUM SIZE
   -> All interactive elements >= 44x44px CSS (WCAG) / 48x48dp (Material)
   -> Measure actual rendered size, not just CSS dimensions
   -> Include padding in clickable area
   -> Touch targets in mobile viewport particularly critical

2. SPACING
   -> At least 8px between adjacent touch targets
   -> No overlapping hit areas
   -> Sufficient spacing in navigation menus
   -> Sufficient spacing in form buttons

3. COMMON VIOLATIONS
   -> Close buttons (X) on modals/alerts — often too small
   -> Inline text links — tap area is just the text
   -> Icon buttons without padding
   -> Checkbox/radio native inputs (use custom with larger target)
   -> Breadcrumb links
   -> Pagination links
   -> Table row actions

4. MOBILE-SPECIFIC
   -> Test at smallest supported viewport
   -> Bottom navigation targets adequately sized
   -> Floating action buttons adequately sized
   -> Swipe gestures have alternative tap targets
```

---

## PHASE 14: MOTION AND ANIMATION PREFERENCES

> *"Motion makes your site feel alive. For some users, it makes them feel sick."*

```
1. PREFERS-REDUCED-MOTION
   -> CSS: @media (prefers-reduced-motion: reduce) defined
   -> All CSS animations/transitions reduced or removed
   -> JavaScript animations respect the preference
   -> Page transitions simplified or removed
   -> Loading spinners replaced with static indicators

2. AUTO-PLAYING CONTENT
   -> No auto-playing video (or has pause control)
   -> No auto-playing audio (ever)
   -> Carousels/slideshows have pause button
   -> Auto-scrolling content has stop mechanism
   -> Animated backgrounds can be stopped

3. FLASHING CONTENT
   -> No content flashes > 3 times per second
   -> No strobing effects
   -> Warning before content with flashing
   -> Safe animation speeds (> 333ms per cycle)

4. PARALLAX AND SCROLL EFFECTS
   -> Parallax effects disabled with reduced motion
   -> Scroll-triggered animations respect preference
   -> Sticky/fixed elements don't cause motion
   -> Smooth scrolling can be disabled

5. TRANSITION DURATION
   -> Important transitions under 5 seconds
   -> No animations that block interaction
   -> Skeleton screens preferred over spinners
   -> Loading states don't animate excessively
```

---

## PHASE 15: COGNITIVE LOAD

> *"If it takes a PhD to understand your interface, your interface has a PhD-level accessibility problem."*

```
1. INFORMATION ARCHITECTURE
   -> Navigation has 7 or fewer top-level items
   -> Content hierarchy is clear and consistent
   -> Related items grouped visually and programmatically
   -> Progressive disclosure for complex features

2. LANGUAGE AND INSTRUCTIONS
   -> Instructions use plain language
   -> Technical jargon has explanations
   -> Error messages explain in user terms (not error codes)
   -> Action buttons describe the action ("Save changes" not "Submit")

3. MEMORY DEMANDS
   -> Multi-step processes show progress
   -> Forms preserve user input on error
   -> Important information visible (not hidden in tooltips)
   -> Copy-paste not blocked on any field
   -> Session timeout warning with extension option

4. CONSISTENCY
   -> Same action, same pattern across pages
   -> Same terminology used consistently
   -> Navigation position consistent
   -> Button styles consistent per action type

5. ERROR RECOVERY
   -> Undo available for destructive actions
   -> Clear path back from dead ends
   -> Auto-save for long forms
   -> Confirmation before irreversible actions
```

---

## PHASE 16: READING LEVEL

> *"WCAG says lower secondary education. That's roughly grade 8. Most web copy reads at grade 12+."*

```
1. READABILITY METRICS
   -> Flesch-Kincaid Grade Level (target: <= 8)
   -> Flesch Reading Ease Score (target: >= 60)
   -> SMOG Index
   -> Gunning Fog Index
   -> Measure for: hero text, instructions, error messages, tooltips

2. PLAIN LANGUAGE AUDIT
   -> Active voice preferred over passive
   -> Short sentences (< 20 words average)
   -> Short paragraphs (< 5 sentences)
   -> Common words over technical jargon
   -> Abbreviations spelled out on first use

3. TEXT FORMATTING
   -> Body text 16px minimum
   -> Line height 1.5 minimum for body text
   -> Paragraph spacing 1.5x line height
   -> Letter spacing not less than 0.12em
   -> Word spacing not less than 0.16em
   -> Text resizable to 200% without loss

4. CONTENT ALTERNATIVES
   -> Complex procedures have step-by-step format
   -> Data tables have summary text
   -> Legal text has plain language summary
   -> Instructions include examples
```

---

## PHASE H1 — HYBRID SYNTHESIS (Popper / hinge / user-need / edge cases / cross-audit)

> **NEW (2026-05-08, hybrid framework, runs immediately before VERDICT):**
> "H1" = Hybrid step 1 of the synthesis layer that pairs with Phase 0's
> programmatic gather. It does NOT renumber existing phases; it sits between
> the audit's last domain phase and the VERDICT phase. Between the per-domain
> phases above and the VERDICT phase, you must run this 5-step synthesis.
> The token budget freed by Phase 0's deterministic gather is REINVESTED here
> — depth of analysis is what increases. This phase does NOT replace any
> earlier phase; it deepens them.

### 2.1 Popper falsification per finding (mandatory)

For every finding in `evidence-summary.json.findings[]` (start with `severity ∈
{critical, high}`, then go down as time/budget allows), try to PROVE the tool
is wrong. Each falsification produces a `falsifiable_tests[]` entry in
`verdict.json`:

```jsonc
{
  "claim": "ts-prune says src/auth/legacy.ts:42 export `signWithOldKey` is unused",
  "test_command": "grep -rn 'signWithOldKey' --include='*.ts' --include='*.tsx' --include='*.test.ts' --include='*.spec.ts' . | grep -v 'src/auth/legacy.ts'",
  "expected": "0 matches → claim TRUE, finding stands",
  "actual": "0 matches found",
  "outcome": "confirmed"
}
```

Outcomes:
- `confirmed` — Popper test FAILED to falsify → finding stands, often promoted
- `falsified` — Popper test produced a counter-example → demote to `info` and
  add `falsified_at: <evidence>` to the finding
- `inconclusive` — test could not run cleanly → keep severity, mark
  `confidence: medium` on this finding

**The rule:** every CLAIM in the audit (PASS or FAIL) MUST cite ≥3 concrete
commands that COULD have falsified it but didn't. Banned phrases (`looks
correct`, `should be fine`, `appears to work`) → automatic FAIL of the audit.

Common falsification patterns by category:

| Tool says | Popper test |
|---|---|
| `unused export` (ts-prune, vulture) | `grep` for the symbol in tests/, integration/, and dynamic imports (e.g. `import("...")`) |
| `unused dependency` (depcheck) | `grep` `package.json` scripts + `find . -type f -exec grep -l <pkg>` |
| `circular dep` (madge) | Read all files in the cycle — is the cycle real or a tooling artifact (e.g. type-only re-export)? |
| `console.error` / `console.warn` (debugaudit) | Reproduce the user flow that triggers it; if not reachable from any UI path, demote to info |
| `npm audit HIGH` (sec) | Check if the vulnerable code path is actually called in your codebase; not all transitive vulns are exploitable |
| `LCP > 2.5s` (perf) | Re-run lighthouse twice; check if it's a one-off (cold cache, network blip) or systematic |
| `axe-core color-contrast` (a11y) | Manually compute the ratio; some palettes hit 4.4 vs 4.5 — fixable in tokens |
| `missing canonical` (seo) | Check if the page is intentionally not canonical (paginated, faceted) before flagging |
| `unauthenticated endpoint` (api) | Read the route handler — is auth enforced via middleware not visible to the static scan? |
| `orphaned record` (data) | Verify the FK is supposed to cascade, or confirm the parent was deleted by an actual user action |

### 2.2 Hinge cross-reference (10× scrutiny on load-bearing findings)

The HINGE FLOW of this audit is the locus of maximum risk/value. Compute or
read:

```bash
# If a hinge file already exists for this ticket, use it:
HINGE_FILE="$HOME/.omega/state/hinge-points-${TICKET_ID:-default}.json"

# Otherwise compute it on the fly:
${OMEGA_DIR:-$HOME/.omega}/skills/audits/_shared/hinge-analyzer.sh "$PROJECT_PATH" --audit=a11y --user-need="$USER_NEED_QUOTE" \
  > "$HINGE_FILE"
```

For each finding in `evidence-summary.json`, mark
`is_load_bearing: true` IFF its file matches a hinge entry, then apply 10×
scrutiny to those findings:

- 5× more falsification attempts (Phase 2.1)
- 3× more edge-case hunts (Phase 2.4)
- Mandatory read of the entire hinge file (not just the flagged line)
- Mandatory read of all DIRECT callers of the hinge function/symbol
- Mandatory read of all DIRECT callees from the hinge

Output `hinge_findings[]` in `verdict.json`:

```jsonc
{
  "finding_id": "F-042",
  "is_load_bearing": true,
  "hinge_reference": "<hinge term + file>",
  "additional_scrutiny": "verified all 7 callers, all 4 callees, all 12 tests; reproduced bug in 3 of them",
  "confidence_after_scrutiny": "high"
}
```

### 2.3 User-need verification (`--user-need` quote)

If the audit was dispatched with `--user-need="<verbatim user complaint>"`,
every finding MUST be evaluated against it. The user-need is the ground truth
for what counts as "audit succeeded".

For each finding, ask:
- "If a user reported THIS exact issue verbatim, would this finding be the
  cause?"
- "Does fixing this finding make the user-need quote no longer true?"

Findings that DO NOT relate to user-need:
- Get demoted by one severity level UNLESS they are load-bearing
  (Phase 2.2), in which case they retain severity (load-bearing is
  user-need-independent).
- Are still reported, but flagged `user_need_relevance: "tangential"`.

Findings that DO relate to user-need:
- Get the highest priority in the fix plan
- Are listed first in `user_need_match.findings[]` of `verdict.json`

```jsonc
{
  "user_need_match": {
    "addressed": true,
    "user_need_quote": "<verbatim>",
    "rationale": "Finding F-007 (axe-core: input has no associated label) directly causes the user-reported behavior 'I can't tab to the search field'. Fix removes the cause.",
    "findings": ["F-007", "F-019"],
    "untouched_findings_relevant_to_user_need": []
  }
}
```

If `addressed: false`, the audit MUST score below 90/100 even if all phases
otherwise pass. The user's actual problem is the only correct success metric.

### 2.4 Edge case hunting (mandatory for top findings)

For each top-5 finding (sorted by severity × cross-audit-confirmed × hinge),
generate ≥2 edge cases the tool may have missed. The static analyzer or
runtime probe checked the code at rest; you must imagine motion.

Patterns:
- "If user does X under condition Y..." — concurrency, race, double-submit
- "Tool checked the file at rest, but at runtime + concurrent..." — async/await
  ordering, state mutations, cache races
- "Static scan saw the import, but the dynamic require()..." — code-splitting,
  feature-flag gated, lazy()
- "i18n locale switch..." — strings missing in non-default locale
- "Network flake mid-request..." — partial state, retry idempotency
- "User logs out mid-flow..." — session expiration, token refresh
- "Timezone boundary (DST, UTC midnight)..." — date math
- "Data shape: empty array, null, undefined, single-element..." — boundary

Output `edge_cases[]` in `verdict.json`:

```jsonc
{
  "finding_id": "F-007",
  "scenario": "User pastes a multi-line value into the search field; the
    `<input>` strips newlines silently and submits a partial query",
  "covered_by_existing_test": false,
  "evidence_gathered": "Manual repro on prod URL; screenshot at .a11y/edge-evidence/F-007.png",
  "fix_includes_coverage": true
}
```

### 2.5 Cross-audit synthesis (re-read sibling summaries from Phase 0.5)

Now that you have your own `verdict.json` draft, do a final pass with sibling
audits' findings open in context:

1. For each of YOUR top-5 findings: is the same file/line/symbol flagged in any
   sibling audit?
2. If yes → escalate confidence (`cross_audit_confirmed: true`), bump severity
   one level (low→medium, medium→high, high→critical, critical stays).
3. For each sibling top-5 finding: is the same file/line/symbol relevant to
   YOUR audit's domain? If yes, add it to YOUR findings as
   `tool: "cross-audit:<sibling-name>"` with proper severity.
4. Write `cross_audit_links[]` in `verdict.json` summarizing the matches.

```jsonc
{
  "cross_audit_links": [
    {
      "this_finding_id": "F-007",
      "sibling_audit": "secaudit",
      "sibl

…(truncated)
