WCAG 2.2 AA Accessibility
Comprehensive accessibility guidance for web applications targeting WCAG 2.2 Level AA conformance.
Architecture Overview
[Component Authoring]
├── Semantic HTML (landmarks, headings, lists)
├── ARIA attributes (roles, states, properties)
├── Keyboard interaction (focus, tab order, shortcuts)
└── Visual design (contrast, spacing, motion)
↓
[Automated Testing]
├── axe-core / jest-axe (unit)
├── Lighthouse CI (integration)
└── pa11y (page-level)
↓
[Manual Testing]
├── Keyboard-only navigation
├── Screen reader walkthrough (VoiceOver, NVDA)
└── Zoom to 200% / reflow check
↓
[Continuous Compliance]
├── CI gate (axe failures block merge)
├── Design token enforcement (contrast-safe palette)
└── Periodic audit (quarterly full review)
Quick Reference
Decision Matrix
Text Contrast Requirements (WCAG 1.4.3 / 1.4.6)
| Text Type |
AA Minimum |
AAA Target |
How to Check |
| Normal text (<18px) |
4.5:1 |
7:1 |
Foreground vs background color |
| Large text (>=18px) |
3:1 |
4.5:1 |
Foreground vs background color |
| Bold text (>=14px) |
3:1 |
4.5:1 |
Counts as "large" |
| UI components / icons |
3:1 |
N/A |
Against adjacent colors |
| Decorative / disabled |
No req |
No req |
Must look obviously disabled |
| Placeholder text |
4.5:1 |
7:1 |
Treated as normal text by WCAG |
Common Tailwind Opacity Pitfalls
Opacity modifiers on semantic color tokens (e.g., text-muted-foreground/60) reduce contrast below WCAG thresholds. Common failures:
| Pattern |
Typical Ratio |
Verdict |
Fix |
text-foreground |
15:1+ |
Passes AA |
No change needed |
text-muted-foreground |
5-6:1 |
Passes AA |
No change needed |
text-muted-foreground/80 |
4-5:1 |
Borderline |
Use text-muted-foreground |
text-muted-foreground/70 |
3.5-4:1 |
Fails AA |
Use text-muted-foreground |
text-muted-foreground/60 |
2.5-3:1 |
Fails AA |
Use text-muted-foreground |
text-muted-foreground/50 |
2-2.5:1 |
Fails AA |
Use text-muted-foreground |
text-muted-foreground/40 |
1.5-2:1 |
Fails AA |
Use text-muted-foreground |
placeholder:text-*/40 |
1.5-2:1 |
Fails AA |
Use full text-muted-foreground |
Rule of thumb: Never apply opacity below /80 to text tokens. If you need visual hierarchy below text-muted-foreground, the design system's contrast floor has been reached — use size, weight, or spacing instead.
Interactive Element Requirements
| Element |
Keyboard |
Focus Ring |
ARIA |
Contrast |
| Button |
Enter/Space |
Required |
button role |
4.5:1 text, 3:1 boundary |
| Link |
Enter |
Required |
<a> or link role |
4.5:1 + distinguishable |
| Menu / Dropdown |
Arrow keys |
Required |
menu, menuitem |
4.5:1 |
| Tab |
Arrow keys |
Required |
tablist, tab, tabpanel |
4.5:1 |
| Modal / Dialog |
Trap focus |
Required |
dialog, aria-modal |
4.5:1 |
| Toggle / Switch |
Space |
Required |
switch or checkbox |
3:1 boundary |
| Accordion |
Enter/Space |
Required |
region, button |
4.5:1 |
| Form input |
Tab |
Required |
<label> + error |
3:1 boundary |
| Custom widget |
Follows WAI-ARIA pattern |
Required |
Match closest pattern |
All ratios |
Screen Reader Essentials
// Visually hidden but screen-reader accessible
<span className="sr-only">Close dialog</span>
// Live regions for dynamic content
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
// Assertive for errors
<div aria-live="assertive" role="alert">
{errorMessage}
</div>
Core Principles
- Perceivable — Content must be presentable in ways all users can perceive (contrast, alt text, captions)
- Operable — UI must be operable via keyboard, with enough time, no seizure triggers
- Understandable — Content and UI behavior must be understandable (labels, errors, consistent navigation)
- Robust — Content must be interpreted reliably by assistive technologies (semantic HTML, valid ARIA)
Hover/Focus Content (WCAG 1.4.13)
Tooltips, popovers, and any content triggered by hover or focus must be:
// Dismissible — user can close without moving pointer (Escape key)
// Hoverable — user can move pointer over the tooltip without it disappearing
// Persistent — stays visible until user dismisses, moves focus, or info becomes invalid
// PASS — Radix UI Tooltip handles all three requirements
<Tooltip>
<TooltipTrigger>Hover me</TooltipTrigger>
<TooltipContent>
This tooltip is dismissible, hoverable, and persistent
</TooltipContent>
</Tooltip>
// FAIL — disappears when pointer moves away from trigger
<div
=> setShow(true)}
=> setShow(false)} // Can't hover the tooltip content
>
{show && <div className="absolute">Tooltip content</div>}
</div>
// PASS — tooltip stays visible when hovering its content
<div
=> setShow(true)}
=> setShow(false)}
>
Trigger
{show && (
<div
=> setShow(true)} // Keep open when hovering content
=> setShow(false)}
>
Tooltip content
</div>
)}
</div>
Status Messages (WCAG 4.1.3)
Dynamic status updates must be announced to screen readers without receiving focus:
// Toast notifications
<div role="status" aria-live="polite">
{toast && <p>{toast.message}</p>}
</div>
// Search result counts
<div role="status" aria-live="polite" aria-atomic="true">
{results.length} listings found
</div>
// Form submission success
<div role="status">Listing saved successfully</div>
// Error alerts (urgent — use assertive)
<div role="alert">Payment failed. Please try again.</div>
// Loading progress
<div role="status" aria-live="polite">
Uploading... {progress}% complete
</div>
| Situation |
Role |
aria-live |
When |
| Success message |
status |
polite |
After form submit |
| Error message |
alert |
assertive |
On validation/server error |
| Search results count |
status |
polite |
After search completes |
| Loading indicator |
status |
polite |
When async operation starts |
| Toast notification |
status |
polite |
On transient messages |
| Chat message received |
log |
polite |
New messages in feed |
WCAG 2.2 Complete Success Criteria Coverage
All 50 Level A + AA success criteria are covered across the skill resources:
Principle 1: Perceivable
| # |
Name |
Level |
Resource |
| 1.1.1 |
Non-text Content |
A |
images-and-media |
| 1.2.1 |
Audio-only/Video-only |
A |
images-and-media |
| 1.2.2 |
Captions (Prerecorded) |
A |
images-and-media |
| 1.2.3 |
Audio Description or Alternative |
A |
images-and-media |
| 1.2.4 |
Captions (Live) |
AA |
images-and-media |
| 1.2.5 |
Audio Description (Prerecorded) |
AA |
images-and-media |
| 1.3.1 |
Info and Relationships |
A |
semantic-html |
| 1.3.2 |
Meaningful Sequence |
A |
page-structure |
| 1.3.3 |
Sensory Characteristics |
A |
page-structure |
| 1.3.4 |
Orientation |
AA |
page-structure |
| 1.3.5 |
Identify Input Purpose |
AA |
forms-and-inputs |
| 1.4.1 |
Use of Color |
A |
links-and-navigation |
| 1.4.2 |
Audio Control |
A |
timing-and-authentication |
| 1.4.3 |
Contrast (Minimum) |
AA |
color-contrast |
| 1.4.4 |
Resize Text |
AA |
page-structure |
| 1.4.5 |
Images of Text |
AA |
images-and-media |
| 1.4.10 |
Reflow |
AA |
page-structure |
| 1.4.11 |
Non-text Contrast |
AA |
color-contrast |
| 1.4.12 |
Text Spacing |
AA |
page-structure |
| 1.4.13 |
Content on Hover or Focus |
AA |
SKILL.md (above) |
Principle 2: Operable
| # |
Name |
Level |
Resource |
| 2.1.1 |
Keyboard |
A |
keyboard-navigation |
| 2.1.2 |
No Keyboard Trap |
A |
keyboard-navigation |
| 2.1.4 |
Character Key Shortcuts |
A |
timing-and-authentication |
| 2.2.1 |
Timing Adjustable |
A |
timing-and-authentication |
| 2.2.2 |
Pause, Stop, Hide |
A |
motion-and-animation |
| 2.3.1 |
Three Flashes |
A |
motion-and-animation |
| 2.4.1 |
Bypass Blocks |
A |
keyboard-navigation |
| 2.4.2 |
Page Titled |
A |
page-structure |
| 2.4.3 |
Focus Order |
A |
keyboard-navigation |
| 2.4.4 |
Link Purpose (In Context) |
A |
links-and-navigation |
| 2.4.5 |
Multiple Ways |
AA |
links-and-navigation |
| 2.4.6 |
Headings and Labels |
AA |
semantic-html, forms-and-inputs |
| 2.4.7 |
Focus Visible |
AA |
keyboard-navigation |
| 2.4.11 |
Focus Not Obscured |
AA |
keyboard-navigation [NEW 2.2] |
| 2.5.1 |
Pointer Gestures |
A |
pointer-and-touch |
| 2.5.2 |
Pointer Cancellation |
A |
pointer-and-touch |
| 2.5.3 |
Label in Name |
A |
links-and-navigation |
| 2.5.4 |
Motion Actuation |
A |
pointer-and-touch |
| 2.5.7 |
Dragging Movements |
AA |
pointer-and-touch [NEW 2.2] |
| 2.5.8 |
Target Size (Minimum) |
AA |
pointer-and-touch [NEW 2.2] |
Principle 3: Understandable
| # |
Name |
Level |
Resource |
| 3.1.1 |
Language of Page |
A |
page-structure |
| 3.1.2 |
Language of Parts |
AA |
page-structure |
| 3.2.1 |
On Focus |
A |
links-and-navigation |
| 3.2.2 |
On Input |
A |
links-and-navigation |
| 3.2.3 |
Consistent Navigation |
AA |
links-and-navigation |
| 3.2.4 |
Consistent Identification |
AA |
links-and-navigation |
| 3.2.6 |
Consistent Help |
A |
links-and-navigation [NEW 2.2] |
| 3.3.1 |
Error Identification |
A |
forms-and-inputs |
| 3.3.2 |
Labels or Instructions |
A |
forms-and-inputs |
| 3.3.3 |
Error Suggestion |
AA |
forms-and-inputs |
| 3.3.4 |
Error Prevention |
AA |
timing-and-authentication |
| 3.3.7 |
Redundant Entry |
A |
timing-and-authentication [NEW 2.2] |
| 3.3.8 |
Accessible Authentication |
AA |
timing-and-authentication [NEW 2.2] |
Principle 4: Robust
| # |
Name |
Level |
Resource |
| 4.1.2 |
Name, Role, Value |
A |
semantic-html |
| 4.1.3 |
Status Messages |
AA |
SKILL.md (above) |
Note: 4.1.1 Parsing was removed in WCAG 2.2 and is no longer required.
When Reviewing Code
Run this checklist on every component:
1---2name: wcag-accessibility3description: Use when building UI components, reviewing color contrast, adding ARIA attributes, handling keyboard navigation, writing alt text, creating forms, or ensuring WCAG 2.2 AA compliance. Covers color contrast ratios, semantic HTML, focus management, screen reader support, motion preferences, and accessibility auditing.4---56# WCAG 2.2 AA Accessibility78Comprehensive accessibility guidance for web applications targeting WCAG 2.2 Level AA conformance.910## Architecture Overview1112```13[Component Authoring]14 ├── Semantic HTML (landmarks, headings, lists)15 ├── ARIA attributes (roles, states, properties)16 ├── Keyboard interaction (focus, tab order, shortcuts)17 └── Visual design (contrast, spacing, motion)18 ↓19[Automated Testing]20 ├── axe-core / jest-axe (unit)21 ├── Lighthouse CI (integration)22 └── pa11y (page-level)23 ↓24[Manual Testing]25 ├── Keyboard-only navigation26 ├── Screen reader walkthrough (VoiceOver, NVDA)27 └── Zoom to 200% / reflow check28 ↓29[Continuous Compliance]30 ├── CI gate (axe failures block merge)31 ├── Design token enforcement (contrast-safe palette)32 └── Periodic audit (quarterly full review)33```3435## Quick Reference3637| Need to... | See |38| ----------------------------------------- | ---------------------------------------------------------------- |39| Fix color contrast failures | [Color Contrast](./resources/color-contrast.md) |40| Add keyboard support to custom components | [Keyboard Navigation](./resources/keyboard-navigation.md) |41| Choose correct ARIA roles and landmarks | [Semantic HTML](./resources/semantic-html.md) |42| Build accessible forms with validation | [Forms and Inputs](./resources/forms-and-inputs.md) |43| Write good alt text, handle media | [Images and Media](./resources/images-and-media.md) |44| Handle animations and motion safely | [Motion and Animation](./resources/motion-and-animation.md) |45| Set up page lang, titles, reflow, zoom | [Page Structure](./resources/page-structure.md) |46| Fix links, nav consistency, predictability| [Links and Navigation](./resources/links-and-navigation.md) |47| Support drag alternatives, touch targets | [Pointer and Touch](./resources/pointer-and-touch.md) |48| Handle timeouts, auth, CAPTCHAs | [Timing and Authentication](./resources/timing-and-authentication.md) |49| Set up automated a11y testing | [Testing and Auditing](./resources/testing-and-auditing.md) |5051## Decision Matrix5253### Text Contrast Requirements (WCAG 1.4.3 / 1.4.6)5455| Text Type | AA Minimum | AAA Target | How to Check |56| ---------------------- | ---------- | ---------- | ------------------------------ |57| Normal text (<18px) | 4.5:1 | 7:1 | Foreground vs background color |58| Large text (>=18px) | 3:1 | 4.5:1 | Foreground vs background color |59| Bold text (>=14px) | 3:1 | 4.5:1 | Counts as "large" |60| UI components / icons | 3:1 | N/A | Against adjacent colors |61| Decorative / disabled | No req | No req | Must look obviously disabled |62| Placeholder text | 4.5:1 | 7:1 | Treated as normal text by WCAG |6364### Common Tailwind Opacity Pitfalls6566Opacity modifiers on semantic color tokens (e.g., `text-muted-foreground/60`) reduce contrast below WCAG thresholds. Common failures:6768| Pattern | Typical Ratio | Verdict | Fix |69| -------------------------------- | ------------- | ---------- | ---------------------------- |70| `text-foreground` | 15:1+ | Passes AA | No change needed |71| `text-muted-foreground` | 5-6:1 | Passes AA | No change needed |72| `text-muted-foreground/80` | 4-5:1 | Borderline | Use `text-muted-foreground` |73| `text-muted-foreground/70` | 3.5-4:1 | Fails AA | Use `text-muted-foreground` |74| `text-muted-foreground/60` | 2.5-3:1 | Fails AA | Use `text-muted-foreground` |75| `text-muted-foreground/50` | 2-2.5:1 | Fails AA | Use `text-muted-foreground` |76| `text-muted-foreground/40` | 1.5-2:1 | Fails AA | Use `text-muted-foreground` |77| `placeholder:text-*/40` | 1.5-2:1 | Fails AA | Use full `text-muted-foreground` |7879**Rule of thumb**: Never apply opacity below `/80` to text tokens. If you need visual hierarchy below `text-muted-foreground`, the design system's contrast floor has been reached — use size, weight, or spacing instead.8081### Interactive Element Requirements8283| Element | Keyboard | Focus Ring | ARIA | Contrast |84| ------------------- | -------- | ---------- | -------------- | -------- |85| Button | Enter/Space | Required | `button` role | 4.5:1 text, 3:1 boundary |86| Link | Enter | Required | `<a>` or `link` role | 4.5:1 + distinguishable |87| Menu / Dropdown | Arrow keys | Required | `menu`, `menuitem` | 4.5:1 |88| Tab | Arrow keys | Required | `tablist`, `tab`, `tabpanel` | 4.5:1 |89| Modal / Dialog | Trap focus | Required | `dialog`, `aria-modal` | 4.5:1 |90| Toggle / Switch | Space | Required | `switch` or `checkbox` | 3:1 boundary |91| Accordion | Enter/Space | Required | `region`, `button` | 4.5:1 |92| Form input | Tab | Required | `<label>` + error | 3:1 boundary |93| Custom widget | Follows WAI-ARIA pattern | Required | Match closest pattern | All ratios |9495### Screen Reader Essentials9697```tsx98// Visually hidden but screen-reader accessible99<span className="sr-only">Close dialog</span>100101// Live regions for dynamic content102<div aria-live="polite" aria-atomic="true">103 {statusMessage}104</div>105106// Assertive for errors107<div aria-live="assertive" role="alert">108 {errorMessage}109</div>110```111112## Core Principles1131141. **Perceivable** — Content must be presentable in ways all users can perceive (contrast, alt text, captions)1152. **Operable** — UI must be operable via keyboard, with enough time, no seizure triggers1163. **Understandable** — Content and UI behavior must be understandable (labels, errors, consistent navigation)1174. **Robust** — Content must be interpreted reliably by assistive technologies (semantic HTML, valid ARIA)118119### Hover/Focus Content (WCAG 1.4.13)120121Tooltips, popovers, and any content triggered by hover or focus must be:122123```tsx124// Dismissible — user can close without moving pointer (Escape key)125// Hoverable — user can move pointer over the tooltip without it disappearing126// Persistent — stays visible until user dismisses, moves focus, or info becomes invalid127128// PASS — Radix UI Tooltip handles all three requirements129<Tooltip>130 <TooltipTrigger>Hover me</TooltipTrigger>131 <TooltipContent>132 This tooltip is dismissible, hoverable, and persistent133 </TooltipContent>134</Tooltip>135136// FAIL — disappears when pointer moves away from trigger137<div138 onMouseEnter={() => setShow(true)}139 onMouseLeave={() => setShow(false)} // Can't hover the tooltip content140>141 {show && <div className="absolute">Tooltip content</div>}142</div>143144// PASS — tooltip stays visible when hovering its content145<div146 onMouseEnter={() => setShow(true)}147 onMouseLeave={() => setShow(false)}148>149 Trigger150 {show && (151 <div152 onMouseEnter={() => setShow(true)} // Keep open when hovering content153 onMouseLeave={() => setShow(false)}154 >155 Tooltip content156 </div>157 )}158</div>159```160161### Status Messages (WCAG 4.1.3)162163Dynamic status updates must be announced to screen readers without receiving focus:164165```tsx166// Toast notifications167<div role="status" aria-live="polite">168 {toast && <p>{toast.message}</p>}169</div>170171// Search result counts172<div role="status" aria-live="polite" aria-atomic="true">173 {results.length} listings found174</div>175176// Form submission success177<div role="status">Listing saved successfully</div>178179// Error alerts (urgent — use assertive)180<div role="alert">Payment failed. Please try again.</div>181182// Loading progress183<div role="status" aria-live="polite">184 Uploading... {progress}% complete185</div>186```187188| Situation | Role | aria-live | When |189|-----------|------|-----------|------|190| Success message | `status` | `polite` | After form submit |191| Error message | `alert` | `assertive` | On validation/server error |192| Search results count | `status` | `polite` | After search completes |193| Loading indicator | `status` | `polite` | When async operation starts |194| Toast notification | `status` | `polite` | On transient messages |195| Chat message received | `log` | `polite` | New messages in feed |196197## WCAG 2.2 Complete Success Criteria Coverage198199All 50 Level A + AA success criteria are covered across the skill resources:200201### Principle 1: Perceivable202| # | Name | Level | Resource |203|---|------|-------|----------|204| 1.1.1 | Non-text Content | A | images-and-media |205| 1.2.1 | Audio-only/Video-only | A | images-and-media |206| 1.2.2 | Captions (Prerecorded) | A | images-and-media |207| 1.2.3 | Audio Description or Alternative | A | images-and-media |208| 1.2.4 | Captions (Live) | AA | images-and-media |209| 1.2.5 | Audio Description (Prerecorded) | AA | images-and-media |210| 1.3.1 | Info and Relationships | A | semantic-html |211| 1.3.2 | Meaningful Sequence | A | page-structure |212| 1.3.3 | Sensory Characteristics | A | page-structure |213| 1.3.4 | Orientation | AA | page-structure |214| 1.3.5 | Identify Input Purpose | AA | forms-and-inputs |215| 1.4.1 | Use of Color | A | links-and-navigation |216| 1.4.2 | Audio Control | A | timing-and-authentication |217| 1.4.3 | Contrast (Minimum) | AA | color-contrast |218| 1.4.4 | Resize Text | AA | page-structure |219| 1.4.5 | Images of Text | AA | images-and-media |220| 1.4.10 | Reflow | AA | page-structure |221| 1.4.11 | Non-text Contrast | AA | color-contrast |222| 1.4.12 | Text Spacing | AA | page-structure |223| 1.4.13 | Content on Hover or Focus | AA | SKILL.md (above) |224225### Principle 2: Operable226| # | Name | Level | Resource |227|---|------|-------|----------|228| 2.1.1 | Keyboard | A | keyboard-navigation |229| 2.1.2 | No Keyboard Trap | A | keyboard-navigation |230| 2.1.4 | Character Key Shortcuts | A | timing-and-authentication |231| 2.2.1 | Timing Adjustable | A | timing-and-authentication |232| 2.2.2 | Pause, Stop, Hide | A | motion-and-animation |233| 2.3.1 | Three Flashes | A | motion-and-animation |234| 2.4.1 | Bypass Blocks | A | keyboard-navigation |235| 2.4.2 | Page Titled | A | page-structure |236| 2.4.3 | Focus Order | A | keyboard-navigation |237| 2.4.4 | Link Purpose (In Context) | A | links-and-navigation |238| 2.4.5 | Multiple Ways | AA | links-and-navigation |239| 2.4.6 | Headings and Labels | AA | semantic-html, forms-and-inputs |240| 2.4.7 | Focus Visible | AA | keyboard-navigation |241| 2.4.11 | Focus Not Obscured | AA | keyboard-navigation [NEW 2.2] |242| 2.5.1 | Pointer Gestures | A | pointer-and-touch |243| 2.5.2 | Pointer Cancellation | A | pointer-and-touch |244| 2.5.3 | Label in Name | A | links-and-navigation |245| 2.5.4 | Motion Actuation | A | pointer-and-touch |246| 2.5.7 | Dragging Movements | AA | pointer-and-touch [NEW 2.2] |247| 2.5.8 | Target Size (Minimum) | AA | pointer-and-touch [NEW 2.2] |248249### Principle 3: Understandable250| # | Name | Level | Resource |251|---|------|-------|----------|252| 3.1.1 | Language of Page | A | page-structure |253| 3.1.2 | Language of Parts | AA | page-structure |254| 3.2.1 | On Focus | A | links-and-navigation |255| 3.2.2 | On Input | A | links-and-navigation |256| 3.2.3 | Consistent Navigation | AA | links-and-navigation |257| 3.2.4 | Consistent Identification | AA | links-and-navigation |258| 3.2.6 | Consistent Help | A | links-and-navigation [NEW 2.2] |259| 3.3.1 | Error Identification | A | forms-and-inputs |260| 3.3.2 | Labels or Instructions | A | forms-and-inputs |261| 3.3.3 | Error Suggestion | AA | forms-and-inputs |262| 3.3.4 | Error Prevention | AA | timing-and-authentication |263| 3.3.7 | Redundant Entry | A | timing-and-authentication [NEW 2.2] |264| 3.3.8 | Accessible Authentication | AA | timing-and-authentication [NEW 2.2] |265266### Principle 4: Robust267| # | Name | Level | Resource |268|---|------|-------|----------|269| 4.1.2 | Name, Role, Value | A | semantic-html |270| 4.1.3 | Status Messages | AA | SKILL.md (above) |271272> Note: 4.1.1 Parsing was **removed** in WCAG 2.2 and is no longer required.273274## When Reviewing Code275276Run this checklist on every component:277278- [ ] Color contrast meets 4.5:1 for text, 3:1 for UI components279- [ ] No information conveyed by color alone (use icons, patterns, or text too)280- [ ] All interactive elements reachable and operable by keyboard281- [ ] Focus order is logical and visible282- [ ] Images have meaningful `alt` text (or `alt=""` + `aria-hidden` if decorative)283- [ ] Form inputs have associated `<label>` elements284- [ ] Error messages are programmatically associated with inputs (`aria-describedby`)285- [ ] Dynamic content updates announced via `aria-live` regions286- [ ] No content flashes more than 3 times per second287- [ ] Page works at 200% zoom without horizontal scrolling288- [ ] Content reflows at 320px width without horizontal scrolling289- [ ] Touch targets are at least 24x24 CSS pixels (44x44 preferred)290- [ ] Drag operations have single-pointer alternatives291- [ ] Tooltips/popovers are dismissible, hoverable, and persistent292- [ ] `<html lang>` is set correctly293- [ ] Page has unique, descriptive `<title>`294- [ ] DOM order matches visual reading order295- [ ] Links have descriptive text (no "click here")296- [ ] Navigation is consistent across pages297- [ ] Paste is not blocked on password/code fields298- [ ] `prefers-reduced-motion` is respected for all animations