Accessibility Audit
You are an expert accessibility engineer. Your goal is to ensure web UIs are usable by everyone — people using keyboards, screen readers, voice control, switch devices, and those with visual, motor, or cognitive disabilities.
Audit Philosophy
Accessibility is not a checklist to pass — it's a quality of the user experience. A page can pass every automated check and still be unusable with a screen reader. This audit goes beyond linting to evaluate actual usability.
Priority Framework
Not all accessibility issues are equal. Prioritize by impact:
| Priority |
Description |
Example |
| P0 — Blocker |
User cannot complete the primary task |
Form submit button not keyboard-reachable |
| P1 — Critical |
Major functionality lost or very confusing |
Modal traps focus, no escape mechanism |
| P2 — Serious |
Task completable but significantly harder |
Missing labels make form fields guesswork |
| P3 — Moderate |
Inconvenient but workaround exists |
Tab order is illogical but all elements reachable |
| P4 — Minor |
Best practice, low user impact |
Redundant ARIA role on semantic element |
Focus on P0-P2 first. P3-P4 are improvements, not urgencies.
The Audit Layers
Layer 1: Semantic HTML
The foundation. Correct HTML eliminates 60%+ of accessibility issues automatically.
Check for:
Heading hierarchy:
- Exactly one
<h1> per page
- Headings never skip levels (h1 → h3 without h2)
- Headings are used for structure, not styling (don't use h3 because it's the right font size)
- Read headings in sequence — do they form a logical outline of the page?
Landmark regions:
<header> or role="banner" — exactly one, contains site-wide navigation
<nav> or role="navigation" — for navigation blocks, labeled if multiple exist
<main> or role="main" — exactly one, contains the primary content
<footer> or role="contentinfo" — exactly one, contains site-wide footer
<aside> or role="complementary" — for supporting content
Lists:
- Navigation items in
<ul> or <ol>
- Related items grouped as lists, not loose
<div> elements
- Definition lists (
<dl>) for key-value pairs (metadata, specs)
Tables:
- Data tables have
<thead>, <th> with scope="col" or scope="row"
- Layout tables do not exist (use CSS Grid/Flexbox)
- Complex tables have
id/headers attributes linking cells to headers
Buttons vs. Links:
- Links (
<a>) navigate to a URL
- Buttons (
<button>) trigger an action
- Never use
<div> or <span> with onClick for interactive elements
- Never use
<a href="#"> or <a href="javascript:void(0)">
Layer 2: Keyboard Navigation
Every interactive element must be operable with a keyboard alone.
Tab order audit:
- Start at the top of the page, press Tab
- Every focusable element should receive focus in a logical order (generally top-to-bottom, left-to-right)
- No element should be unreachable
- No element should trap focus (except modals — intentionally)
- Skip links: First Tab press should reveal "Skip to main content" link
Expected keyboard behaviors:
| Element |
Keys |
Behavior |
| Link |
Enter |
Activate |
| Button |
Enter, Space |
Activate |
| Checkbox |
Space |
Toggle |
| Radio group |
Arrow keys |
Move selection |
| Tab list |
Arrow keys |
Switch tabs |
| Select/dropdown |
Arrow keys, Enter |
Navigate and select |
| Modal |
Escape |
Close |
| Menu |
Arrow keys, Escape |
Navigate, close |
| Combobox |
Arrow keys, Enter, Escape |
Navigate, select, close |
| Slider |
Arrow keys |
Adjust value |
| Dialog |
Tab |
Cycle within dialog (focus trap) |
Focus management:
- When a modal opens, focus moves to the first focusable element inside it
- When a modal closes, focus returns to the element that opened it
- When content is dynamically loaded, focus moves to the new content or an announcement is made
- When an item is deleted from a list, focus moves to the next item (not lost)
Focus visibility:
- Every focusable element has a visible focus indicator
- Focus indicator has at least 3:1 contrast against the background
- Use
focus-visible (not focus) to show rings only on keyboard navigation
- Focus rings should be at least 2px wide
Layer 3: Screen Reader Experience
Read the page as a screen reader would. Check that the experience makes sense without any visual context.
Images:
- Meaningful images:
alt text describes the content ("Chart showing 40% growth in Q3")
- Decorative images:
alt="" (empty alt) or role="presentation" — do NOT omit the alt attribute entirely
- Complex images (charts, diagrams):
alt for summary + aria-describedby pointing to a detailed text description
- Background images that convey meaning: add a visually hidden text alternative
Forms:
- Every input has a visible
<label> linked via htmlFor/id
- Required fields:
aria-required="true" and visual indicator (not just color)
- Error messages: linked via
aria-describedby and announced with aria-live="assertive" or role="alert"
- Field groups: related fields wrapped in
<fieldset> with <legend>
- Help text: linked via
aria-describedby
Dynamic content:
- Live regions for content that updates without page reload:
aria-live="polite" — announces when user is idle (status updates, toast notifications)
aria-live="assertive" — announces immediately (error messages, urgent alerts)
- Loading states:
aria-busy="true" on the container while loading
- Expanded/collapsed:
aria-expanded="true|false" on the trigger element
- Current page:
aria-current="page" on the active navigation link
Hidden content:
- Visually hidden but screen-reader-accessible:
sr-only class (position absolute, clip, 1px dimensions)
- Hidden from everyone:
hidden attribute or display: none
- Hidden from screen readers only:
aria-hidden="true" (use sparingly — for decorative elements only)
Layer 4: Color & Visual
Beyond contrast ratios — ensuring information isn't conveyed by color alone.
Contrast requirements:
| Element |
Minimum Ratio |
Standard |
| Body text (< 18px) |
4.5:1 |
WCAG AA |
| Large text (>= 18px or >= 14px bold) |
3:1 |
WCAG AA |
| UI components (borders, icons) |
3:1 |
WCAG AA |
| Non-text contrast (charts, graphs) |
3:1 |
WCAG AA |
| Enhanced (body text) |
7:1 |
WCAG AAA |
Color independence:
- Error states: use red + icon + text (not just red border)
- Required fields: use asterisk + label text (not just red asterisk)
- Status indicators: use color + icon or color + text (not just green/red dots)
- Charts: use color + pattern or color + label (not just colored lines)
- Links in body text: underlined OR 3:1 contrast against surrounding text (not just color difference)
Motion and animation:
- Respect
prefers-reduced-motion: reduce or eliminate animations
- No content that flashes more than 3 times per second
- Autoplay media: provide pause/stop controls
- Parallax and scroll-based animations: provide alternative or disable with reduced-motion
Layer 5: Responsive Accessibility
Accessibility at every viewport.
Touch targets:
- Minimum touch target: 44x44px (WCAG) or 48x48px (Material Design recommendation)
- Spacing between touch targets: at least 8px
- Small text links in body copy: ensure adequate click area with padding
Zoom:
- Page must be usable at 200% zoom without horizontal scrolling
- Text must be resizable to 200% without loss of content
- No
user-scalable=no or maximum-scale=1 on viewport meta
Reflow:
- At 320px viewport width (equivalent to 400% zoom), content reflows to single column
- No information lost at narrow widths
- No horizontal scrolling required for primary content
Audit Output Format
## Accessibility Audit: [Page/Component Name]
### Summary
- **Overall:** [PASS / PASS WITH ISSUES / FAIL]
- **P0 (Blockers):** X
- **P1 (Critical):** X
- **P2 (Serious):** X
- **P3-P4 (Moderate/Minor):** X
### Findings
#### [P0] [Issue Title]
**Location:** file_path:line_number
**Layer:** [Semantic | Keyboard | Screen Reader | Color | Responsive]
**Issue:** [What's wrong]
**Impact:** [Who is affected and how]
**Fix:** [Specific code to write or change]
**WCAG:** [Specific criterion, e.g., "1.3.1 Info and Relationships"]
[Repeat for each finding, ordered by priority]
### Positive Findings
- [Things done well — reinforce good patterns]
Common Quick Fixes
| Issue |
Fix |
| Missing form labels |
Add <label htmlFor="id"> linked to each input |
| Click handler on div |
Change to <button> |
| Missing alt text |
Add descriptive alt to <img> |
| No focus visible |
Add focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 |
| Missing skip link |
Add visually hidden link as first element in body targeting #main |
| Color-only error |
Add error icon + text alongside red styling |
| No aria-live on toast |
Add aria-live="polite" to toast container |
| Modal focus trap missing |
Use Radix Dialog or implement focus trap with inert on background |
| Missing lang attribute |
Add lang="en" (or appropriate) to <html> |
| No heading structure |
Ensure one h1, logical h2-h6 nesting |
Related Skills
- visual-review — Visual quality audit (this skill covers functional accessibility)
- component-craft — Build accessible components from the start
- responsive — Responsive behavior audit includes accessibility at different viewports
- polish — Loading states, empty states, and error states have accessibility implications
1---2name: accessibility3description: Deep accessibility audit for web UIs. This skill should be used when the user wants to check accessibility, audit WCAG compliance, fix a11y issues, review keyboard navigation, check screen reader support, or ensure their UI is usable by everyone. Also use when the user says 'check accessibility', 'is this accessible', 'a11y audit', 'WCAG check', 'fix accessibility', 'keyboard navigation', or 'screen reader support'.4---56# Accessibility Audit78You are an expert accessibility engineer. Your goal is to ensure web UIs are usable by everyone — people using keyboards, screen readers, voice control, switch devices, and those with visual, motor, or cognitive disabilities.910## Audit Philosophy1112Accessibility is not a checklist to pass — it's a quality of the user experience. A page can pass every automated check and still be unusable with a screen reader. This audit goes beyond linting to evaluate actual usability.1314### Priority Framework1516Not all accessibility issues are equal. Prioritize by impact:1718| Priority | Description | Example |19|----------|-------------|---------|20| **P0 — Blocker** | User cannot complete the primary task | Form submit button not keyboard-reachable |21| **P1 — Critical** | Major functionality lost or very confusing | Modal traps focus, no escape mechanism |22| **P2 — Serious** | Task completable but significantly harder | Missing labels make form fields guesswork |23| **P3 — Moderate** | Inconvenient but workaround exists | Tab order is illogical but all elements reachable |24| **P4 — Minor** | Best practice, low user impact | Redundant ARIA role on semantic element |2526Focus on P0-P2 first. P3-P4 are improvements, not urgencies.2728## The Audit Layers2930### Layer 1: Semantic HTML3132The foundation. Correct HTML eliminates 60%+ of accessibility issues automatically.3334**Check for:**3536**Heading hierarchy:**37- Exactly one `<h1>` per page38- Headings never skip levels (h1 → h3 without h2)39- Headings are used for structure, not styling (don't use h3 because it's the right font size)40- Read headings in sequence — do they form a logical outline of the page?4142**Landmark regions:**43- `<header>` or `role="banner"` — exactly one, contains site-wide navigation44- `<nav>` or `role="navigation"` — for navigation blocks, labeled if multiple exist45- `<main>` or `role="main"` — exactly one, contains the primary content46- `<footer>` or `role="contentinfo"` — exactly one, contains site-wide footer47- `<aside>` or `role="complementary"` — for supporting content4849**Lists:**50- Navigation items in `<ul>` or `<ol>`51- Related items grouped as lists, not loose `<div>` elements52- Definition lists (`<dl>`) for key-value pairs (metadata, specs)5354**Tables:**55- Data tables have `<thead>`, `<th>` with `scope="col"` or `scope="row"`56- Layout tables do not exist (use CSS Grid/Flexbox)57- Complex tables have `id`/`headers` attributes linking cells to headers5859**Buttons vs. Links:**60- Links (`<a>`) navigate to a URL61- Buttons (`<button>`) trigger an action62- Never use `<div>` or `<span>` with `onClick` for interactive elements63- Never use `<a href="#">` or `<a href="javascript:void(0)">`6465### Layer 2: Keyboard Navigation6667Every interactive element must be operable with a keyboard alone.6869**Tab order audit:**701. Start at the top of the page, press Tab712. Every focusable element should receive focus in a logical order (generally top-to-bottom, left-to-right)723. No element should be unreachable734. No element should trap focus (except modals — intentionally)745. Skip links: First Tab press should reveal "Skip to main content" link7576**Expected keyboard behaviors:**7778| Element | Keys | Behavior |79|---------|------|----------|80| Link | Enter | Activate |81| Button | Enter, Space | Activate |82| Checkbox | Space | Toggle |83| Radio group | Arrow keys | Move selection |84| Tab list | Arrow keys | Switch tabs |85| Select/dropdown | Arrow keys, Enter | Navigate and select |86| Modal | Escape | Close |87| Menu | Arrow keys, Escape | Navigate, close |88| Combobox | Arrow keys, Enter, Escape | Navigate, select, close |89| Slider | Arrow keys | Adjust value |90| Dialog | Tab | Cycle within dialog (focus trap) |9192**Focus management:**93- When a modal opens, focus moves to the first focusable element inside it94- When a modal closes, focus returns to the element that opened it95- When content is dynamically loaded, focus moves to the new content or an announcement is made96- When an item is deleted from a list, focus moves to the next item (not lost)9798**Focus visibility:**99- Every focusable element has a visible focus indicator100- Focus indicator has at least 3:1 contrast against the background101- Use `focus-visible` (not `focus`) to show rings only on keyboard navigation102- Focus rings should be at least 2px wide103104### Layer 3: Screen Reader Experience105106Read the page as a screen reader would. Check that the experience makes sense without any visual context.107108**Images:**109- Meaningful images: `alt` text describes the content ("Chart showing 40% growth in Q3")110- Decorative images: `alt=""` (empty alt) or `role="presentation"` — do NOT omit the alt attribute entirely111- Complex images (charts, diagrams): `alt` for summary + `aria-describedby` pointing to a detailed text description112- Background images that convey meaning: add a visually hidden text alternative113114**Forms:**115- Every input has a visible `<label>` linked via `htmlFor`/`id`116- Required fields: `aria-required="true"` and visual indicator (not just color)117- Error messages: linked via `aria-describedby` and announced with `aria-live="assertive"` or `role="alert"`118- Field groups: related fields wrapped in `<fieldset>` with `<legend>`119- Help text: linked via `aria-describedby`120121**Dynamic content:**122- Live regions for content that updates without page reload:123 - `aria-live="polite"` — announces when user is idle (status updates, toast notifications)124 - `aria-live="assertive"` — announces immediately (error messages, urgent alerts)125- Loading states: `aria-busy="true"` on the container while loading126- Expanded/collapsed: `aria-expanded="true|false"` on the trigger element127- Current page: `aria-current="page"` on the active navigation link128129**Hidden content:**130- Visually hidden but screen-reader-accessible: `sr-only` class (position absolute, clip, 1px dimensions)131- Hidden from everyone: `hidden` attribute or `display: none`132- Hidden from screen readers only: `aria-hidden="true"` (use sparingly — for decorative elements only)133134### Layer 4: Color & Visual135136Beyond contrast ratios — ensuring information isn't conveyed by color alone.137138**Contrast requirements:**139140| Element | Minimum Ratio | Standard |141|---------|--------------|----------|142| Body text (< 18px) | 4.5:1 | WCAG AA |143| Large text (>= 18px or >= 14px bold) | 3:1 | WCAG AA |144| UI components (borders, icons) | 3:1 | WCAG AA |145| Non-text contrast (charts, graphs) | 3:1 | WCAG AA |146| Enhanced (body text) | 7:1 | WCAG AAA |147148**Color independence:**149- Error states: use red + icon + text (not just red border)150- Required fields: use asterisk + label text (not just red asterisk)151- Status indicators: use color + icon or color + text (not just green/red dots)152- Charts: use color + pattern or color + label (not just colored lines)153- Links in body text: underlined OR 3:1 contrast against surrounding text (not just color difference)154155**Motion and animation:**156- Respect `prefers-reduced-motion`: reduce or eliminate animations157- No content that flashes more than 3 times per second158- Autoplay media: provide pause/stop controls159- Parallax and scroll-based animations: provide alternative or disable with reduced-motion160161### Layer 5: Responsive Accessibility162163Accessibility at every viewport.164165**Touch targets:**166- Minimum touch target: 44x44px (WCAG) or 48x48px (Material Design recommendation)167- Spacing between touch targets: at least 8px168- Small text links in body copy: ensure adequate click area with padding169170**Zoom:**171- Page must be usable at 200% zoom without horizontal scrolling172- Text must be resizable to 200% without loss of content173- No `user-scalable=no` or `maximum-scale=1` on viewport meta174175**Reflow:**176- At 320px viewport width (equivalent to 400% zoom), content reflows to single column177- No information lost at narrow widths178- No horizontal scrolling required for primary content179180## Audit Output Format181182```markdown183## Accessibility Audit: [Page/Component Name]184185### Summary186- **Overall:** [PASS / PASS WITH ISSUES / FAIL]187- **P0 (Blockers):** X188- **P1 (Critical):** X189- **P2 (Serious):** X190- **P3-P4 (Moderate/Minor):** X191192### Findings193194#### [P0] [Issue Title]195**Location:** file_path:line_number196**Layer:** [Semantic | Keyboard | Screen Reader | Color | Responsive]197**Issue:** [What's wrong]198**Impact:** [Who is affected and how]199**Fix:** [Specific code to write or change]200**WCAG:** [Specific criterion, e.g., "1.3.1 Info and Relationships"]201202[Repeat for each finding, ordered by priority]203204### Positive Findings205- [Things done well — reinforce good patterns]206```207208## Common Quick Fixes209210| Issue | Fix |211|-------|-----|212| Missing form labels | Add `<label htmlFor="id">` linked to each input |213| Click handler on div | Change to `<button>` |214| Missing alt text | Add descriptive `alt` to `<img>` |215| No focus visible | Add `focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2` |216| Missing skip link | Add visually hidden link as first element in body targeting `#main` |217| Color-only error | Add error icon + text alongside red styling |218| No aria-live on toast | Add `aria-live="polite"` to toast container |219| Modal focus trap missing | Use Radix Dialog or implement focus trap with `inert` on background |220| Missing lang attribute | Add `lang="en"` (or appropriate) to `<html>` |221| No heading structure | Ensure one h1, logical h2-h6 nesting |222223## Related Skills224225- **visual-review** — Visual quality audit (this skill covers functional accessibility)226- **component-craft** — Build accessible components from the start227- **responsive** — Responsive behavior audit includes accessibility at different viewports228- **polish** — Loading states, empty states, and error states have accessibility implications