| name | Accessibility Audit |
| description | ENFORCE WCAG 2.2 AA compliance. Use semantic HTML first. Provide visible focus indicators. Label all inputs. Ensure contrast ratios. Test with keyboard. Prevent keyboard traps, invisible focus, low contrast, unlabeled inputs, and non-semantic HTML. Trigger: "make it accessible", "fix a11y issues", "audit for accessibility", "ensure WCAG compliance".
|
| category | quality |
| version | 3.0.0 |
| last_updated | 2026-06-28T00:00:00.000Z |
| stacks | ["HTML","React 19.2","Vue 3","Tailwind CSS v4"] |
| related_skills | ["component-architecture-patterns","dashboard-information-architecture","no-slop-landing-page-architecture"] |
Accessibility Audit Protocol
IDENTIFY: When to Activate
Activate:
- CONTINUOUSLY while building UI components
- EXPLICITLY when asked to review/audit for accessibility
- When creating any interactive element: buttons, forms, navigation, modals
- When setting colors, verify contrast ratios
CORE PRINCIPLE
Semantic HTML first. ARIA supplements, never replaces. Test every component with keyboard before mouse. Every interactive element must be focusable and labeled.
EXECUTE: Six Audit Categories
1. Semantic HTML: ALWAYS Use Native Elements
<button onClick={...}>Save</button>
<nav>Main navigation</nav>
<main>Primary content</main>
<label htmlFor="email">Email</label>
<div onClick={...} role="button" tabIndex={0}>Save</div>
<div class="nav">Navigation</div>
DETECTION RULE: If you find yourself adding role and tabIndex to a <div>, you're using the wrong element. Use the native element instead.
2. Focus Management: ALWAYS Show Visible Focus
button:focus { outline: none; }
button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--color-primary);
}
Tailwind v4:
<button class="focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none">
TABINDEX RULES:
tabindex="0", adds to natural tab order (rarely needed)
tabindex="-1", focusable via JS but not Tab (modals, skip links)
- NEVER use
tabindex="1", tabindex="2", etc., overrides natural order
3. Labels and Descriptions: EVERY Input, Button, Image
Inputs (in priority order):
<label htmlFor="email">Email address</label>
<input id="email" type="email" />
<input aria-label="Search" type="search" />
<input type="email" placeholder="Enter email" />
Icon-only buttons:
<button aria-label="Close dialog"><XIcon aria-hidden="true" /></button>
<button><XIcon aria-hidden="true" /><span class="sr-only">Close dialog</span></button>
Images:
<img src="chart.png" alt="Revenue grew 23% year-over-year" />
<img src="decorative-wave.svg" alt="" />
<img src="chart.png" />
4. Color and Contrast: WCAG 2.2 AA Ratios
| Text Type | Minimum Contrast | When |
|---|
| Normal text (body, labels) | ≥ 4.5:1 | All body text |
| Large text (≥24px or ≥18.7px bold) | ≥ 3:1 | Headings, large labels |
| UI components (borders, icons) | ≥ 3:1 | Interactive element borders |
NOTE: APCA is NOT part of WCAG 2.2 (proposed for WCAG 3.0, not expected until 2028+). Use WCAG 2.2 ratio formula.
NEVER rely on color alone:
<span style="color: green">Success</span>
<span style="color: red">Error</span>
<span style="color: green">✓ Success</span>
<span style="color: red">✗ Error</span>
5. Touch Targets: WCAG 2.2 AA Minimum
MINIMUM: 24×24px (WCAG 2.2 AA Target Size criterion 2.5.8)
RECOMMENDED: 44×44px (WCAG AAA, Apple HIG, Material Design)
6. ARIA: Use Sparingly, Only When HTML Insufficient
| Scenario | ARIA to Use |
|---|
| Accordion/disclosure | aria-expanded on trigger |
| Icon-only button | aria-label on button |
| Dynamic content (notifications) | aria-live="polite" on container |
| Error → input linkage | aria-describedby on input |
<nav role="navigation" aria-label="Main navigation">
<nav aria-label="Main">
Running Automated Checks
RUN ESLint with jsx-a11y during development:
npm install -D eslint-plugin-jsx-a11y
.eslintrc.json: { "extends": ["plugin:jsx-a11y/recommended"] }
RUN axe DevTools (Chrome extension) for page-level audit:
Manual: Open DevTools → axe tab → Analyze
RUN Lighthouse Accessibility audit:
Target: 100/100
VALIDATE: Quality Gates
ANTI-PATTERNS: ALWAYS Avoid
| Anti-Pattern | Why Wrong | Fix |
|---|
<div onClick={handler}> | Not keyboard-focusable. SR doesn't announce as interactive. | Use <button> |
outline: none without alternative | Keyboard users can't see focus | focus-visible:ring-2 |
placeholder instead of <label> | SR ignores placeholders. Placeholder disappears on type. | ALWAYS use <label> |
tabindex="1", tabindex="2" | Overrides natural tab order. Maintenance nightmare. | Only tabindex="0" or -1 |
| ARIA everywhere | Redundant ARIA creates noise | Native HTML first. ARIA only when insufficient. |