Accessibility Review
Evaluate UI code for WCAG 2.1 Level AA compliance, organized by the POUR principles: Perceivable, Operable, Understandable, Robust.
Cross-reference: frontend/knowledge-accessibility for WCAG guidelines, ARIA patterns, and semantic HTML.
When to Use
- Before merging UI changes (new components, layout changes, interactive elements)
- When building forms, modals, navigation, or data tables
- During periodic accessibility audits
- Before launching user-facing features
Severity Levels
| Level |
Meaning |
| CRITICAL |
WCAG 2.1 AA failure — content is inaccessible to some users, legal risk — fix before merge |
| WARNING |
Usability issue for assistive tech users — fix soon |
| SUGGESTION |
Enhancement that improves experience — implement when practical |
Review Checklist by POUR Principle
Perceivable
Can users perceive all content regardless of ability?
| Check |
WCAG |
Severity |
Images have descriptive alt text (decorative images use alt="") |
1.1.1 |
CRITICAL |
| Video/audio has captions or transcripts |
1.2.1 |
CRITICAL |
| Color is not the sole means of conveying information |
1.4.1 |
CRITICAL |
| Text color contrast ratio ≥ 4.5:1 (normal text), ≥ 3:1 (large text) |
1.4.3 |
CRITICAL |
| Non-text contrast (icons, borders, form controls) ≥ 3:1 |
1.4.11 |
WARNING |
| Text can be resized to 200% without loss of content |
1.4.4 |
WARNING |
| Content reflows at 320px width without horizontal scroll |
1.4.10 |
WARNING |
| No information conveyed only through sensory characteristics ("click the red button") |
1.3.3 |
WARNING |
| Content order is meaningful in DOM (matches visual order) |
1.3.2 |
WARNING |
Operable
Can users navigate and interact using keyboard, voice, or assistive tech?
| Check |
WCAG |
Severity |
| All interactive elements are keyboard accessible |
2.1.1 |
CRITICAL |
| No keyboard traps (user can always Tab/Escape out) |
2.1.2 |
CRITICAL |
| Focus order follows logical reading sequence |
2.4.3 |
CRITICAL |
Focus indicator is visible (not suppressed via outline: none without replacement) |
2.4.7 |
CRITICAL |
| Skip-to-main-content link provided |
2.4.1 |
WARNING |
| Page titles are descriptive |
2.4.2 |
WARNING |
| Link text is descriptive (no "click here") |
2.4.4 |
WARNING |
| Touch targets ≥ 44x44 CSS pixels |
2.5.5 |
WARNING |
Animations respect prefers-reduced-motion |
2.3.3 |
WARNING |
| No content that flashes more than 3 times per second |
2.3.1 |
CRITICAL |
Understandable
Can users understand content and predict interface behavior?
| Check |
WCAG |
Severity |
lang attribute set on <html> |
3.1.1 |
WARNING |
Form inputs have associated <label> elements |
3.3.2 |
CRITICAL |
| Error messages identify the field and describe the error |
3.3.1 |
CRITICAL |
| Error suggestions provided when possible |
3.3.3 |
WARNING |
| Instructions don't rely solely on visual position |
3.3.2 |
WARNING |
| Navigation is consistent across pages |
3.2.3 |
WARNING |
| No unexpected context changes on focus or input |
3.2.1/2 |
WARNING |
Robust
Does the content work with current and future assistive technologies?
| Check |
WCAG |
Severity |
| HTML is valid (proper nesting, no duplicate IDs) |
4.1.1 |
WARNING |
| Custom components have correct ARIA roles, states, and properties |
4.1.2 |
CRITICAL |
ARIA attributes match component state (e.g., aria-expanded toggles) |
4.1.2 |
CRITICAL |
Status messages use role="status" or aria-live |
4.1.3 |
WARNING |
Semantic HTML used over generic <div>/<span> with ARIA |
4.1.2 |
WARNING |
Common Issues
ARIA Misuse
| Issue |
Fix |
role="button" on a <div> without keyboard handler |
Use <button> instead, or add tabindex="0" + keydown handler for Enter/Space |
aria-label on non-interactive element |
Move to the interactive element or use aria-describedby |
aria-hidden="true" on focusable element |
Remove from tab order or remove aria-hidden |
Redundant ARIA (role="navigation" on <nav>) |
Remove the redundant role — native semantics are sufficient |
Focus Management
| Scenario |
Required Behavior |
| Modal opens |
Focus moves to modal, trapped inside, returns to trigger on close |
| Dynamic content added |
Announce via aria-live region or move focus to new content |
| Route change (SPA) |
Focus moves to main content or page heading |
| Item deleted from list |
Focus moves to next item or parent container |
| Inline edit activated |
Focus moves to input, returns to trigger on save/cancel |
Form Patterns
| Issue |
Fix |
| Placeholder as only label |
Add visible <label> or aria-label |
| Required field with no indicator |
Add aria-required="true" and visible indicator |
| Error shown but not announced |
Use aria-describedby linking input to error, aria-invalid="true" |
Grouped controls (radio/checkbox) without <fieldset>/<legend> |
Wrap in <fieldset> with <legend> |
Output Format
## Accessibility Review: [Component/Feature Name]
**Scope**: [What was reviewed]
**Standard**: WCAG 2.1 Level AA
**Overall**: [PASS | PASS WITH WARNINGS | FAIL]
### Findings by POUR Principle
#### Perceivable
##### [CRITICAL] Missing alt text on product images
**Location**: `src/components/ProductCard.tsx:23`
**WCAG**: 1.1.1 Non-text Content
**Issue**: `<img>` has no `alt` attribute
**Impact**: Screen reader users cannot identify the product
**Fix**: Add descriptive alt: `<img alt={product.name} ... />`
...
### Summary
| Principle | Critical | Warning | Suggestion |
|-----------|----------|---------|------------|
| Perceivable | 1 | 1 | 0 |
| Operable | 0 | 2 | 0 |
| Understandable | 1 | 0 | 1 |
| Robust | 0 | 1 | 0 |
| **Total** | **2** | **4** | **1** |
### Testing Recommendations
- [ ] Test with screen reader (VoiceOver/NVDA)
- [ ] Test keyboard-only navigation
- [ ] Run axe-core automated audit
- [ ] Check color contrast with browser devtools
1---2name: review-accessibility3description: Audit for WCAG 2.1 AA compliance covering semantic HTML, ARIA, keyboard navigation, color contrast, screen readers, and focus management. Checklist organized by POUR principles.4---56# Accessibility Review78Evaluate UI code for WCAG 2.1 Level AA compliance, organized by the POUR principles: Perceivable, Operable, Understandable, Robust.910**Cross-reference**: `frontend/knowledge-accessibility` for WCAG guidelines, ARIA patterns, and semantic HTML.1112## When to Use1314- Before merging UI changes (new components, layout changes, interactive elements)15- When building forms, modals, navigation, or data tables16- During periodic accessibility audits17- Before launching user-facing features1819## Severity Levels2021| Level | Meaning |22|-------|---------|23| **CRITICAL** | WCAG 2.1 AA failure — content is inaccessible to some users, legal risk — fix before merge |24| **WARNING** | Usability issue for assistive tech users — fix soon |25| **SUGGESTION** | Enhancement that improves experience — implement when practical |2627## Review Checklist by POUR Principle2829### Perceivable3031_Can users perceive all content regardless of ability?_3233| Check | WCAG | Severity |34|-------|------|----------|35| Images have descriptive `alt` text (decorative images use `alt=""`) | 1.1.1 | CRITICAL |36| Video/audio has captions or transcripts | 1.2.1 | CRITICAL |37| Color is not the sole means of conveying information | 1.4.1 | CRITICAL |38| Text color contrast ratio ≥ 4.5:1 (normal text), ≥ 3:1 (large text) | 1.4.3 | CRITICAL |39| Non-text contrast (icons, borders, form controls) ≥ 3:1 | 1.4.11 | WARNING |40| Text can be resized to 200% without loss of content | 1.4.4 | WARNING |41| Content reflows at 320px width without horizontal scroll | 1.4.10 | WARNING |42| No information conveyed only through sensory characteristics ("click the red button") | 1.3.3 | WARNING |43| Content order is meaningful in DOM (matches visual order) | 1.3.2 | WARNING |4445### Operable4647_Can users navigate and interact using keyboard, voice, or assistive tech?_4849| Check | WCAG | Severity |50|-------|------|----------|51| All interactive elements are keyboard accessible | 2.1.1 | CRITICAL |52| No keyboard traps (user can always Tab/Escape out) | 2.1.2 | CRITICAL |53| Focus order follows logical reading sequence | 2.4.3 | CRITICAL |54| Focus indicator is visible (not suppressed via `outline: none` without replacement) | 2.4.7 | CRITICAL |55| Skip-to-main-content link provided | 2.4.1 | WARNING |56| Page titles are descriptive | 2.4.2 | WARNING |57| Link text is descriptive (no "click here") | 2.4.4 | WARNING |58| Touch targets ≥ 44x44 CSS pixels | 2.5.5 | WARNING |59| Animations respect `prefers-reduced-motion` | 2.3.3 | WARNING |60| No content that flashes more than 3 times per second | 2.3.1 | CRITICAL |6162### Understandable6364_Can users understand content and predict interface behavior?_6566| Check | WCAG | Severity |67|-------|------|----------|68| `lang` attribute set on `<html>` | 3.1.1 | WARNING |69| Form inputs have associated `<label>` elements | 3.3.2 | CRITICAL |70| Error messages identify the field and describe the error | 3.3.1 | CRITICAL |71| Error suggestions provided when possible | 3.3.3 | WARNING |72| Instructions don't rely solely on visual position | 3.3.2 | WARNING |73| Navigation is consistent across pages | 3.2.3 | WARNING |74| No unexpected context changes on focus or input | 3.2.1/2 | WARNING |7576### Robust7778_Does the content work with current and future assistive technologies?_7980| Check | WCAG | Severity |81|-------|------|----------|82| HTML is valid (proper nesting, no duplicate IDs) | 4.1.1 | WARNING |83| Custom components have correct ARIA roles, states, and properties | 4.1.2 | CRITICAL |84| ARIA attributes match component state (e.g., `aria-expanded` toggles) | 4.1.2 | CRITICAL |85| Status messages use `role="status"` or `aria-live` | 4.1.3 | WARNING |86| Semantic HTML used over generic `<div>`/`<span>` with ARIA | 4.1.2 | WARNING |8788## Common Issues8990### ARIA Misuse9192| Issue | Fix |93|-------|-----|94| `role="button"` on a `<div>` without keyboard handler | Use `<button>` instead, or add `tabindex="0"` + `keydown` handler for Enter/Space |95| `aria-label` on non-interactive element | Move to the interactive element or use `aria-describedby` |96| `aria-hidden="true"` on focusable element | Remove from tab order or remove `aria-hidden` |97| Redundant ARIA (`role="navigation"` on `<nav>`) | Remove the redundant role — native semantics are sufficient |9899### Focus Management100101| Scenario | Required Behavior |102|----------|-------------------|103| Modal opens | Focus moves to modal, trapped inside, returns to trigger on close |104| Dynamic content added | Announce via `aria-live` region or move focus to new content |105| Route change (SPA) | Focus moves to main content or page heading |106| Item deleted from list | Focus moves to next item or parent container |107| Inline edit activated | Focus moves to input, returns to trigger on save/cancel |108109### Form Patterns110111| Issue | Fix |112|-------|-----|113| Placeholder as only label | Add visible `<label>` or `aria-label` |114| Required field with no indicator | Add `aria-required="true"` and visible indicator |115| Error shown but not announced | Use `aria-describedby` linking input to error, `aria-invalid="true"` |116| Grouped controls (radio/checkbox) without `<fieldset>`/`<legend>` | Wrap in `<fieldset>` with `<legend>` |117118## Output Format119120```markdown121## Accessibility Review: [Component/Feature Name]122123**Scope**: [What was reviewed]124**Standard**: WCAG 2.1 Level AA125**Overall**: [PASS | PASS WITH WARNINGS | FAIL]126127### Findings by POUR Principle128129#### Perceivable130##### [CRITICAL] Missing alt text on product images131**Location**: `src/components/ProductCard.tsx:23`132**WCAG**: 1.1.1 Non-text Content133**Issue**: `<img>` has no `alt` attribute134**Impact**: Screen reader users cannot identify the product135**Fix**: Add descriptive alt: `<img alt={product.name} ... />`136137...138139### Summary140| Principle | Critical | Warning | Suggestion |141|-----------|----------|---------|------------|142| Perceivable | 1 | 1 | 0 |143| Operable | 0 | 2 | 0 |144| Understandable | 1 | 0 | 1 |145| Robust | 0 | 1 | 0 |146| **Total** | **2** | **4** | **1** |147148### Testing Recommendations149- [ ] Test with screen reader (VoiceOver/NVDA)150- [ ] Test keyboard-only navigation151- [ ] Run axe-core automated audit152- [ ] Check color contrast with browser devtools153```