Accessibility Auditor
Systematic WCAG 2.2 AA audit protocol organized by the four accessibility principles: Perceivable, Operable, Understandable, and Robust (POUR).
When to Apply
Use this skill when:
- Auditing a page, screen, or component for WCAG 2.2 AA compliance
- Reviewing a PR for accessibility regressions
- Building new interactive components that need accessibility
- Preparing for an accessibility compliance review
- Fixing reported accessibility issues
Audit Protocol
Principle 1: Perceivable
Information and UI components must be presentable in ways users can perceive.
| Criterion |
What to Check |
Automated? |
| 1.1.1 Non-text Content |
All <img> have alt; decorative images have alt="" |
Yes |
| 1.3.1 Info and Relationships |
Semantic HTML, heading hierarchy, form labels |
Partial |
| 1.3.2 Meaningful Sequence |
DOM order matches visual order |
Manual |
| 1.3.3 Sensory Characteristics |
Instructions don't rely solely on shape, size, or position |
Manual |
| 1.4.1 Use of Color |
Information not conveyed by color alone |
Manual |
| 1.4.3 Contrast (Minimum) |
4.5:1 normal text, 3:1 large text |
Yes |
| 1.4.4 Resize Text |
Text resizable to 200% without loss |
Manual |
| 1.4.5 Images of Text |
No text rendered as images (except logos) |
Yes |
| 1.4.11 Non-text Contrast |
3:1 for UI components and graphical objects |
Partial |
Principle 2: Operable
UI components and navigation must be operable.
| Criterion |
What to Check |
Automated? |
| 2.1.1 Keyboard |
All functionality available via keyboard |
Manual |
| 2.1.2 No Keyboard Trap |
Focus can be moved away from any component |
Manual |
| 2.4.1 Bypass Blocks |
Skip-to-content link present |
Yes |
| 2.4.2 Page Titled |
Descriptive <title> on every page |
Yes |
| 2.4.3 Focus Order |
Logical tab order |
Manual |
| 2.4.4 Link Purpose |
Link text describes destination (no "click here") |
Partial |
| 2.4.6 Headings and Labels |
Descriptive headings, no skipped levels |
Yes |
| 2.4.7 Focus Visible |
Visible focus indicator on all focusable elements |
Partial |
| 2.4.11 Focus Not Obscured (2.2) |
Focused element not fully hidden by other content |
Manual |
| 2.4.13 Focus Appearance (2.2, AAA — house rule, not required for AA) |
Focus indicator has 3:1 contrast, 2px minimum |
Partial |
| 2.5.7 Dragging Movements (2.2) |
Drag operations have single-pointer alternative |
Manual |
| 2.5.8 Target Size (2.2) |
Interactive targets minimum 24x24px (44x44px mobile) |
Yes |
Principle 3: Understandable
Information and UI operation must be understandable.
| Criterion |
What to Check |
Automated? |
| 3.1.1 Language of Page |
lang attribute on <html> element |
Yes |
| 3.1.2 Language of Parts |
lang attribute on foreign language content |
Manual |
| 3.2.1 On Focus |
No context change on focus alone |
Manual |
| 3.2.2 On Input |
No unexpected context change on input |
Manual |
| 3.3.1 Error Identification |
Errors described in text, not just color |
Manual |
| 3.3.2 Labels or Instructions |
Form inputs have labels; complex inputs have instructions |
Yes |
| 3.2.6 Consistent Help (2.2) |
Help mechanisms in same relative location across pages |
Manual |
| 3.3.7 Redundant Entry (2.2) |
Previously entered info auto-populated or selectable |
Manual |
| 3.3.8 Accessible Authentication (2.2) |
No cognitive function test for auth (allow paste, managers) |
Manual |
Principle 4: Robust
Content must be robust enough for assistive technologies.
| Criterion |
What to Check |
Automated? |
| 4.1.2 Name, Role, Value |
Custom components have proper ARIA roles/states |
Partial |
| 4.1.3 Status Messages |
Dynamic content uses aria-live regions |
Partial |
Automated Checks
Run these checks programmatically:
Code-Level (Grep/Read)
Check Pattern to Search
───────────────────────────── ──────────────────────────────
Missing alt text <img without alt=
Div as button <div.*onClick (without role="button")
Missing form labels <input without associated <label>
Empty links <a>...</a> with no text content
Missing lang attribute <html without lang=
Heading hierarchy h1, h2, h3 order (no skipped levels)
Focus outline removed outline: none without replacement
Hardcoded color as info Color-only status indicators
Token contrast is computable — do not send it to a browser
Audit the tokens at their source, not only the rendered page. A design token pair is two static
HSL values in a file; the WCAG ratio between them is arithmetic, so it belongs in a test, not in a
manual DevTools pass. Nobody opens DevTools 36 times, which is exactly how a contrast failure
survives: not because it is subtle, but because checking it by hand does not scale and so does not
happen.
Every --x / --x-foreground pair in globals.css (or wherever the tokens live) is a case:
// The formula and a ready-made contrastRatio() live in
// @skills/std-design-system/references/defining-tokens.md — import, do not re-derive.
import { contrastRatio } from './check-contrast';
import { light, dark } from './tokens';
// Iterate the PAIRS — do not hand-write two cases and call it covered.
const PAIRS = ['primary', 'success', 'error', 'warning', 'info'] as const;
describe.each([['light', light], ['dark', dark]])('%s token contrast', (_name, theme) => {
it.each(PAIRS)('%s-foreground on %s clears 4.5:1', (token) => {
expect(contrastRatio(theme[`${token}Foreground`], theme[token])).toBeGreaterThanOrEqual(4.5);
});
it('muted-foreground on muted clears 4.5:1', () => {
expect(contrastRatio(theme.mutedForeground, theme.muted)).toBeGreaterThanOrEqual(4.5);
});
});
Two traps this catches that a page-level audit does not:
- A
-foreground token is verified against its SOLID surface. bg-success/10 text-success-foreground is a 10% tint under near-white text — around 1:1, invisible — and no
static check of the token pair sees it, because the pair itself is fine. On a tint, use
text-foreground.
- Presets and themes are copies. If the project ships more than one theme, every theme is a
case. A fix applied to the spec does not reach the preset beside it.
Tool-Based
| Tool |
Purpose |
Integration |
axe-core / jest-axe |
Automated WCAG checks in Vitest |
npm run test |
| Token contrast test (above) |
Every token pair, every theme |
npm run test — not manual |
| Chrome DevTools |
Accessibility tree, live contrast spot-checks |
Manual |
| VoiceOver (macOS) |
Screen reader testing |
Manual |
| NVDA (Windows) |
Screen reader testing |
Manual |
| Lighthouse |
Accessibility score |
CI pipeline |
Output Format
Present findings as:
[SEVERITY] | File:Line | WCAG Criterion | Finding | Remediation
───────────┼──────────┼────────────────┼─────────┼────────────
CRITICAL | path:42 | 2.4.7 | No focus indicator on submit button | Add focus-visible:ring-2 focus-visible:ring-ring
MAJOR | path:15 | 1.1.1 | <img> missing alt text | Add descriptive alt or alt="" for decorative
MINOR | path:88 | 2.4.4 | Link text is "click here" | Use descriptive text: "View order details"
Severity Levels:
- CRITICAL: Blocks user access entirely (no keyboard access, missing essential alt text)
- MAJOR: Significantly impairs experience (low contrast, missing labels)
- MINOR: Degrades experience but workaround exists (non-descriptive link text)
- INFO: Best practice recommendation (could improve but compliant)
Summary Metrics
End each audit with:
| Metric |
Value |
| Total checks performed |
X |
| CRITICAL issues |
X |
| MAJOR issues |
X |
| MINOR issues |
X |
| Estimated compliance |
X% (passing checks / total checks) |
| WCAG 2.2 AA conformance |
Yes / No (any CRITICAL = No) |
Full References
references/wcag-22-checklist.md — Complete WCAG 2.2 AA checklist by principle
references/aria-patterns.md — ARIA widget patterns with keyboard specifications
Owned elsewhere
std-accessibility is the WCAG rule set — it is scoped to .tsx/.jsx under
src/, app/, and components/. Note the edge it does not cover: its paths: are component
files, so it does not load on globals.css or a token file. Contrast decisions get made
where the tokens are defined, and that is std-design-system's territory (it is scoped to
**/globals.css, **/styles/**, **/tailwind.config.*). Auditing a colour system means opening
both.
@skills/std-design-system/references/defining-tokens.md — the contrastRatio()
implementation, the Vitest wiring, and the rule for which side to fix: darken the foreground
for a brand token (the surface is the brand decision), darken the surface for a semantic
status token (which green is not the convention; "green means success" is).
@skills/theming/references/design-tokens.md — the canonical token values and their measured
ratios. @skills/theming/references/theme-presets.md — three presets meant to be copied
wholesale, which is why each is audited as its own theme.
accessibility-checker.py warns on .tsx/.jsx/.css/.scss (semantic HTML, alt text, label
association, focus indicators, ARIA misuse). It does not compute contrast — that is the test
above, because the ratio needs the token values, not the markup.
1---2name: accessibility-auditor3description: WCAG 2.2 AA accessibility audit with 4-principle framework, automated checks, color contrast analysis, keyboard navigation testing, and ARIA pattern validation. Triggers on "accessibility audit", "WCAG audit", "a11y audit", "accessibility check", "screen reader test", "keyboard accessibility", or "accessibility compliance".4---56# Accessibility Auditor78Systematic WCAG 2.2 AA audit protocol organized by the four accessibility principles: Perceivable, Operable, Understandable, and Robust (POUR).910## When to Apply1112Use this skill when:13- Auditing a page, screen, or component for WCAG 2.2 AA compliance14- Reviewing a PR for accessibility regressions15- Building new interactive components that need accessibility16- Preparing for an accessibility compliance review17- Fixing reported accessibility issues1819## Audit Protocol2021### Principle 1: Perceivable2223Information and UI components must be presentable in ways users can perceive.2425| Criterion | What to Check | Automated? |26|-----------|---------------|-----------|27| 1.1.1 Non-text Content | All `<img>` have `alt`; decorative images have `alt=""` | Yes |28| 1.3.1 Info and Relationships | Semantic HTML, heading hierarchy, form labels | Partial |29| 1.3.2 Meaningful Sequence | DOM order matches visual order | Manual |30| 1.3.3 Sensory Characteristics | Instructions don't rely solely on shape, size, or position | Manual |31| 1.4.1 Use of Color | Information not conveyed by color alone | Manual |32| 1.4.3 Contrast (Minimum) | 4.5:1 normal text, 3:1 large text | Yes |33| 1.4.4 Resize Text | Text resizable to 200% without loss | Manual |34| 1.4.5 Images of Text | No text rendered as images (except logos) | Yes |35| 1.4.11 Non-text Contrast | 3:1 for UI components and graphical objects | Partial |3637### Principle 2: Operable3839UI components and navigation must be operable.4041| Criterion | What to Check | Automated? |42|-----------|---------------|-----------|43| 2.1.1 Keyboard | All functionality available via keyboard | Manual |44| 2.1.2 No Keyboard Trap | Focus can be moved away from any component | Manual |45| 2.4.1 Bypass Blocks | Skip-to-content link present | Yes |46| 2.4.2 Page Titled | Descriptive `<title>` on every page | Yes |47| 2.4.3 Focus Order | Logical tab order | Manual |48| 2.4.4 Link Purpose | Link text describes destination (no "click here") | Partial |49| 2.4.6 Headings and Labels | Descriptive headings, no skipped levels | Yes |50| 2.4.7 Focus Visible | Visible focus indicator on all focusable elements | Partial |51| **2.4.11 Focus Not Obscured** (2.2) | Focused element not fully hidden by other content | Manual |52| **2.4.13 Focus Appearance** (2.2, **AAA** — house rule, not required for AA) | Focus indicator has 3:1 contrast, 2px minimum | Partial |53| **2.5.7 Dragging Movements** (2.2) | Drag operations have single-pointer alternative | Manual |54| **2.5.8 Target Size** (2.2) | Interactive targets minimum 24x24px (44x44px mobile) | Yes |5556### Principle 3: Understandable5758Information and UI operation must be understandable.5960| Criterion | What to Check | Automated? |61|-----------|---------------|-----------|62| 3.1.1 Language of Page | `lang` attribute on `<html>` element | Yes |63| 3.1.2 Language of Parts | `lang` attribute on foreign language content | Manual |64| 3.2.1 On Focus | No context change on focus alone | Manual |65| 3.2.2 On Input | No unexpected context change on input | Manual |66| 3.3.1 Error Identification | Errors described in text, not just color | Manual |67| 3.3.2 Labels or Instructions | Form inputs have labels; complex inputs have instructions | Yes |68| **3.2.6 Consistent Help** (2.2) | Help mechanisms in same relative location across pages | Manual |69| **3.3.7 Redundant Entry** (2.2) | Previously entered info auto-populated or selectable | Manual |70| **3.3.8 Accessible Authentication** (2.2) | No cognitive function test for auth (allow paste, managers) | Manual |7172### Principle 4: Robust7374Content must be robust enough for assistive technologies.7576| Criterion | What to Check | Automated? |77|-----------|---------------|-----------|78| 4.1.2 Name, Role, Value | Custom components have proper ARIA roles/states | Partial |79| 4.1.3 Status Messages | Dynamic content uses `aria-live` regions | Partial |8081## Automated Checks8283Run these checks programmatically:8485### Code-Level (Grep/Read)86```87Check Pattern to Search88───────────────────────────── ──────────────────────────────89Missing alt text <img without alt=90Div as button <div.*onClick (without role="button")91Missing form labels <input without associated <label>92Empty links <a>...</a> with no text content93Missing lang attribute <html without lang=94Heading hierarchy h1, h2, h3 order (no skipped levels)95Focus outline removed outline: none without replacement96Hardcoded color as info Color-only status indicators97```9899### Token contrast is computable — do not send it to a browser100101**Audit the tokens at their source, not only the rendered page.** A design token pair is two static102HSL values in a file; the WCAG ratio between them is arithmetic, so it belongs in a test, not in a103manual DevTools pass. Nobody opens DevTools 36 times, which is exactly how a contrast failure104survives: not because it is subtle, but because checking it by hand does not scale and so does not105happen.106107Every `--x` / `--x-foreground` pair in `globals.css` (or wherever the tokens live) is a case:108109```ts110// The formula and a ready-made contrastRatio() live in111// @skills/std-design-system/references/defining-tokens.md — import, do not re-derive.112import { contrastRatio } from './check-contrast';113import { light, dark } from './tokens';114115// Iterate the PAIRS — do not hand-write two cases and call it covered.116const PAIRS = ['primary', 'success', 'error', 'warning', 'info'] as const;117118describe.each([['light', light], ['dark', dark]])('%s token contrast', (_name, theme) => {119 it.each(PAIRS)('%s-foreground on %s clears 4.5:1', (token) => {120 expect(contrastRatio(theme[`${token}Foreground`], theme[token])).toBeGreaterThanOrEqual(4.5);121 });122 it('muted-foreground on muted clears 4.5:1', () => {123 expect(contrastRatio(theme.mutedForeground, theme.muted)).toBeGreaterThanOrEqual(4.5);124 });125});126```127128Two traps this catches that a page-level audit does not:129130- **A `-foreground` token is verified against its SOLID surface.** `bg-success/10131 text-success-foreground` is a 10% tint under near-white text — around 1:1, invisible — and no132 static check of the *token pair* sees it, because the pair itself is fine. On a tint, use133 `text-foreground`.134- **Presets and themes are copies.** If the project ships more than one theme, every theme is a135 case. A fix applied to the spec does not reach the preset beside it.136137### Tool-Based138| Tool | Purpose | Integration |139|------|---------|-------------|140| `axe-core` / `jest-axe` | Automated WCAG checks in Vitest | `npm run test` |141| Token contrast test (above) | Every token pair, every theme | `npm run test` — **not manual** |142| Chrome DevTools | Accessibility tree, live contrast spot-checks | Manual |143| VoiceOver (macOS) | Screen reader testing | Manual |144| NVDA (Windows) | Screen reader testing | Manual |145| Lighthouse | Accessibility score | CI pipeline |146147## Output Format148149Present findings as:150151```152[SEVERITY] | File:Line | WCAG Criterion | Finding | Remediation153───────────┼──────────┼────────────────┼─────────┼────────────154CRITICAL | path:42 | 2.4.7 | No focus indicator on submit button | Add focus-visible:ring-2 focus-visible:ring-ring155MAJOR | path:15 | 1.1.1 | <img> missing alt text | Add descriptive alt or alt="" for decorative156MINOR | path:88 | 2.4.4 | Link text is "click here" | Use descriptive text: "View order details"157```158159**Severity Levels:**160- **CRITICAL**: Blocks user access entirely (no keyboard access, missing essential alt text)161- **MAJOR**: Significantly impairs experience (low contrast, missing labels)162- **MINOR**: Degrades experience but workaround exists (non-descriptive link text)163- **INFO**: Best practice recommendation (could improve but compliant)164165## Summary Metrics166167End each audit with:168169| Metric | Value |170|--------|-------|171| Total checks performed | X |172| CRITICAL issues | X |173| MAJOR issues | X |174| MINOR issues | X |175| Estimated compliance | X% (passing checks / total checks) |176| WCAG 2.2 AA conformance | Yes / No (any CRITICAL = No) |177178## Full References179180- `references/wcag-22-checklist.md` — Complete WCAG 2.2 AA checklist by principle181- `references/aria-patterns.md` — ARIA widget patterns with keyboard specifications182183### Owned elsewhere184185- **`std-accessibility`** is the WCAG rule set — it is **scoped to `.tsx`/`.jsx`** under186 `src/`, `app/`, and `components/`. Note the edge it does *not* cover: its `paths:` are component187 files, so it does **not** load on `globals.css` or a token file. Contrast decisions get made188 where the tokens are defined, and that is `std-design-system`'s territory (it is scoped to189 `**/globals.css`, `**/styles/**`, `**/tailwind.config.*`). Auditing a colour system means opening190 both.191- **`@skills/std-design-system/references/defining-tokens.md`** — the `contrastRatio()`192 implementation, the Vitest wiring, and the rule for *which* side to fix: darken the **foreground**193 for a brand token (the surface is the brand decision), darken the **surface** for a semantic194 status token (which green is not the convention; "green means success" is).195- **`@skills/theming/references/design-tokens.md`** — the canonical token values and their measured196 ratios. **`@skills/theming/references/theme-presets.md`** — three presets meant to be copied197 wholesale, which is why each is audited as its own theme.198- `accessibility-checker.py` warns on `.tsx`/`.jsx`/`.css`/`.scss` (semantic HTML, alt text, label199 association, focus indicators, ARIA misuse). It does **not** compute contrast — that is the test200 above, because the ratio needs the token values, not the markup.