@softspark/ai-toolkit 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +32 -19
- package/CHANGELOG.md +23 -0
- package/README.md +11 -11
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/agents/code-reviewer.md +6 -7
- package/app/agents/frontend-specialist.md +33 -2
- package/app/agents/seo-specialist.md +1 -1
- package/app/personas/frontend-lead.md +48 -5
- package/app/skills/a11y-validate/SKILL.md +377 -0
- package/app/skills/a11y-validate/reference/aria-patterns.md +259 -0
- package/app/skills/a11y-validate/reference/eaa-compliance.md +252 -0
- package/app/skills/a11y-validate/reference/mobile-eaa.md +329 -0
- package/app/skills/a11y-validate/reference/wcag-2-1-aa.md +285 -0
- package/app/skills/a11y-validate/reference/wcag-2-2-aa.md +221 -0
- package/app/skills/a11y-validate/scripts/a11y-scanner.py +639 -0
- package/app/skills/clean-code/reference/python.md +3 -3
- package/app/skills/design-engineering/SKILL.md +2 -5
- package/app/skills/review/SKILL.md +30 -6
- package/app/skills/seo-validate/SKILL.md +460 -0
- package/app/skills/seo-validate/reference/core-web-vitals.md +445 -0
- package/app/skills/seo-validate/reference/geo-aeo-patterns.md +259 -0
- package/app/skills/seo-validate/reference/geo-guidelines.md +248 -0
- package/app/skills/seo-validate/reference/schema-types.md +465 -0
- package/app/skills/seo-validate/reference/spa-ssg-patterns.md +351 -0
- package/app/skills/seo-validate/reference/w3c-guidelines.md +289 -0
- package/app/skills/seo-validate/scripts/seo-scanner.py +549 -0
- package/bin/ai-toolkit.js +4 -4
- package/kb/reference/architecture-overview.md +1 -1
- package/kb/reference/comparison.md +1 -1
- package/kb/reference/skills-catalog.md +3 -1
- package/llms-full.txt +10 -6
- package/manifest.json +3 -3
- package/package.json +2 -2
- package/scripts/config_cli.py +4 -10
- package/scripts/doctor.py +3 -3
- package/scripts/install_steps/ai_tools.py +1 -1
- package/scripts/plugin.py +1 -1
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: a11y-validate
|
|
3
|
+
description: "Validate code against accessibility standards: WCAG 2.1 Level AA, EN 301 549, and the European Accessibility Act (EAA / Directive EU 2019/882). Covers semantics, text alternatives, keyboard/focus, color/contrast, forms, media, ARIA, motion, mobile, and EAA documentation requirements. Framework-aware (React/Next/Nuxt/Astro/Gatsby/SvelteKit/Remix/Angular/Vue/React Native/Flutter/static HTML)."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
effort: medium
|
|
6
|
+
disable-model-invocation: true
|
|
7
|
+
context: fork
|
|
8
|
+
agent: frontend-specialist
|
|
9
|
+
argument-hint: "[path] [--scope full|keyboard|contrast|forms|media|aria|motion|mobile|docs] [--standard wcag-2.1-aa|wcag-2.2-aa|en-301-549|eaa] [--severity high|warn|info] [--framework auto|react|next|nuxt|astro|gatsby|sveltekit|remix|angular|vue|react-native|flutter|static] [--output markdown|json]"
|
|
10
|
+
allowed-tools: Read, Grep, Glob, Bash
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# /a11y-validate — Accessibility & EAA Compliance Scanner
|
|
14
|
+
|
|
15
|
+
$ARGUMENTS
|
|
16
|
+
|
|
17
|
+
Scan a codebase for accessibility issues using pattern-matching heuristics. Detects violations of **WCAG 2.1 Level AA**, **EN 301 549** (the EU harmonized accessibility standard), and the **European Accessibility Act** (Directive (EU) 2019/882, "EAA", in force since 28 June 2025). Read-only — never modifies files.
|
|
18
|
+
|
|
19
|
+
Complements `/seo-validate` (which only covers SEO-a11y overlap shallowly). Use this skill when the concern is legal accessibility compliance, not search engine ranking.
|
|
20
|
+
|
|
21
|
+
**Standards basis**:
|
|
22
|
+
- **WCAG 2.1** Level A + AA — W3C Recommendation 2018 (updated 2023).
|
|
23
|
+
- **WCAG 2.2** Level AA (opt-in via `--standard wcag-2.2-aa`) — adds 2.4.11 focus not obscured, 2.5.8 target size minimum, 3.2.6 consistent help, 3.3.7 redundant entry, 3.3.8 accessible authentication.
|
|
24
|
+
- **EN 301 549 v3.2.1** — EU harmonized standard; aligned with WCAG 2.1 AA plus additional chapters for mobile, hardware, ICT procurement, authoring tools, and functional-performance statements.
|
|
25
|
+
- **EAA / Directive (EU) 2019/882** — legal framework requiring EN 301 549 conformance for consumer-facing digital products and services in EU markets. Deadline: **28 June 2025**. Requires accessibility statements per member-state templates.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
/a11y-validate # Scan full project, auto-detect framework
|
|
31
|
+
/a11y-validate src/ # Scan specific path
|
|
32
|
+
/a11y-validate --scope keyboard # Only keyboard + focus checks
|
|
33
|
+
/a11y-validate --scope media # Only captions/transcripts/autoplay
|
|
34
|
+
/a11y-validate --scope docs # Only EAA accessibility-statement check
|
|
35
|
+
/a11y-validate --scope mobile # Only React Native + Flutter patterns
|
|
36
|
+
/a11y-validate --standard eaa # Activate EAA documentation category
|
|
37
|
+
/a11y-validate --standard wcag-2.2-aa # Add WCAG 2.2 criteria
|
|
38
|
+
/a11y-validate --severity high # Filter to HIGH findings
|
|
39
|
+
/a11y-validate --framework react-native # Force framework
|
|
40
|
+
/a11y-validate --output json # Structured JSON for CI integration
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Scopes:**
|
|
44
|
+
- `full` (default) — all 8 categories
|
|
45
|
+
- `keyboard` — Category 3 only
|
|
46
|
+
- `contrast` — Category 4 only
|
|
47
|
+
- `forms` — Category 5 only
|
|
48
|
+
- `media` — Category 6 only
|
|
49
|
+
- `aria` — Category 7 only
|
|
50
|
+
- `motion` — Category 8 motion subsection
|
|
51
|
+
- `mobile` — Category 8 mobile subsection (React Native / Flutter)
|
|
52
|
+
- `docs` — Category 8 EAA documentation subsection (fast "are we legally exposed?" scan)
|
|
53
|
+
|
|
54
|
+
**Standards:**
|
|
55
|
+
- `wcag-2.1-aa` (default) — 50 Level A + AA success criteria.
|
|
56
|
+
- `wcag-2.2-aa` — adds 2.4.11, 2.5.8, 3.2.6, 3.3.7, 3.3.8.
|
|
57
|
+
- `en-301-549` — wcag-2.1-aa + mobile chapter + functional-performance statements.
|
|
58
|
+
- `eaa` — en-301-549 + accessibility-statement documentation requirement (activates Category 8 docs).
|
|
59
|
+
|
|
60
|
+
**Severity filtering:** `--severity high` shows only HIGH, `--severity warn` shows HIGH+WARN, `--severity info` shows all. Default: all.
|
|
61
|
+
|
|
62
|
+
## What This Command Does
|
|
63
|
+
|
|
64
|
+
1. **Detect framework** from `package.json`, `pubspec.yaml`, and entry HTML.
|
|
65
|
+
2. **Scan the codebase** using `Grep` / `Glob` / `Read` against framework-aware patterns per category in scope.
|
|
66
|
+
3. **Interpret findings** with specific fixes tied to the detected framework.
|
|
67
|
+
4. **Report** findings sorted by severity with WCAG / EN 301 549 citations.
|
|
68
|
+
|
|
69
|
+
## Steps
|
|
70
|
+
|
|
71
|
+
### Step 1: Detect Framework
|
|
72
|
+
|
|
73
|
+
Run detection before scanning. Same logic as `/seo-validate` plus mobile entries:
|
|
74
|
+
|
|
75
|
+
| Deps / files contain | Framework | Notes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `next` | `next` | App Router uses `metadata` export |
|
|
78
|
+
| `nuxt` | `nuxt` | `useHead()` / `definePageMeta` |
|
|
79
|
+
| `astro` | `astro` | islands model; `client:only` affects a11y |
|
|
80
|
+
| `gatsby` | `gatsby` | Head API + react-helmet |
|
|
81
|
+
| `@sveltejs/kit` | `sveltekit` | `<svelte:head>` |
|
|
82
|
+
| `@remix-run/*` | `remix` | `MetaFunction` |
|
|
83
|
+
| `@angular/core` | `angular` | CDK `a11y` module expected |
|
|
84
|
+
| `vue` (no nuxt) | `vue` | a11y plugins optional |
|
|
85
|
+
| `react` + `vite` (no next/remix) | `react-spa` | — |
|
|
86
|
+
| `react-scripts` | `cra` | — |
|
|
87
|
+
| `react-native` | `react-native` | mobile — AccessibilityInfo API |
|
|
88
|
+
| `pubspec.yaml` with Flutter SDK | `flutter` | mobile — `Semantics()` widget |
|
|
89
|
+
| no framework deps | `static` | raw HTML |
|
|
90
|
+
|
|
91
|
+
Also detect a11y libraries: `@react-aria/*`, `@reach/*`, `@angular/cdk/a11y`, `vue-a11y`, `svelte-a11y`, `react-axe`, `axe-core`. Their presence is INFO.
|
|
92
|
+
|
|
93
|
+
### Step 2: Run Category Scans
|
|
94
|
+
|
|
95
|
+
For each category in `--scope`, apply the pattern set below using `Grep` + `Read`. Patterns adapt to the detected framework.
|
|
96
|
+
|
|
97
|
+
### Step 3: Interpret and Enrich
|
|
98
|
+
|
|
99
|
+
For each finding:
|
|
100
|
+
1. **Read the flagged file/lines** to confirm the match.
|
|
101
|
+
2. **Add a framework-specific fix** (e.g., "use `@react-aria/button`" vs "add `aria-label`").
|
|
102
|
+
3. **Mark confidence** — `definitive` for regex matches, `heuristic` for co-occurrence / absence / target-size estimation.
|
|
103
|
+
4. **Skip false positives** when context shows the concern is addressed (e.g., aria-label set via intl translation key).
|
|
104
|
+
|
|
105
|
+
### Step 4: Report
|
|
106
|
+
|
|
107
|
+
Present findings sorted by severity (HIGH → WARN → INFO), then file path.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Scanner Reference
|
|
112
|
+
|
|
113
|
+
### Category 1: Semantic Structure & Landmarks
|
|
114
|
+
|
|
115
|
+
WCAG 1.3.1 (Info and Relationships), 2.4.1 (Bypass Blocks), 2.4.6 (Headings and Labels), 3.1.1 (Language of Page), 3.1.2 (Language of Parts).
|
|
116
|
+
|
|
117
|
+
| Pattern | Severity | Confidence | Description |
|
|
118
|
+
|---------|----------|------------|-------------|
|
|
119
|
+
| `<html>` missing `lang` attribute | HIGH | definitive | WCAG 3.1.1 |
|
|
120
|
+
| Mixed-language content without `<span lang="...">` wrapper (heuristic: non-Latin characters in otherwise-Latin content) | WARN | heuristic | WCAG 3.1.2 |
|
|
121
|
+
| Page/route component with >1 `<h1>` | WARN | heuristic | WCAG 1.3.1 |
|
|
122
|
+
| Heading level skip (h1 → h3 without h2) | WARN | heuristic | WCAG 1.3.1 |
|
|
123
|
+
| No landmark roles / semantic elements (`<main>`, `<nav>`, `<header>`, `<footer>`) | WARN | heuristic | WCAG 1.3.1, 2.4.1 |
|
|
124
|
+
| `role="presentation"` / `role="none"` on semantic element | WARN | definitive | Strips meaning; misuse of ARIA |
|
|
125
|
+
| Multiple `<main>` per page | HIGH | definitive | WCAG 1.3.1 — only one `<main>` allowed |
|
|
126
|
+
|
|
127
|
+
### Category 2: Text Alternatives & Non-Text Content
|
|
128
|
+
|
|
129
|
+
WCAG 1.1.1 (Non-text Content).
|
|
130
|
+
|
|
131
|
+
| Pattern | Severity | Confidence | Description |
|
|
132
|
+
|---------|----------|------------|-------------|
|
|
133
|
+
| `<img>` without `alt` attribute | HIGH | definitive | WCAG 1.1.1 — required even if empty |
|
|
134
|
+
| `<img alt="">` on informational image (heuristic: image inside `<article>`, `<figure>`, or with adjacent caption) | WARN | heuristic | Empty alt only for decorative |
|
|
135
|
+
| `<img alt="image">` / `<img alt="photo">` / `<img alt="picture">` (redundant/meaningless) | WARN | definitive | Alt should describe content |
|
|
136
|
+
| `<svg>` without `<title>` + `role="img"` + `aria-label`, used in interactive context | WARN | heuristic | Inline SVG needs alternative |
|
|
137
|
+
| Icon font (`<i class="fa-...">`, `<span class="material-icons">`) without `aria-label` or text alternative | WARN | definitive | WCAG 1.1.1 |
|
|
138
|
+
| `<img>` used for text content (heuristic: `alt` contains a full sentence like "Click here to...") | WARN | heuristic | WCAG 1.4.5 Images of Text |
|
|
139
|
+
| Complex image (`<img>` with `src` matching `chart|graph|diagram|infographic`) without long description (`aria-describedby` or `longdesc` or linked description) | WARN | heuristic | WCAG 1.1.1 for complex content |
|
|
140
|
+
|
|
141
|
+
### Category 3: Keyboard & Focus
|
|
142
|
+
|
|
143
|
+
WCAG 2.1.1 (Keyboard), 2.1.2 (No Keyboard Trap), 2.4.3 (Focus Order), 2.4.7 (Focus Visible).
|
|
144
|
+
|
|
145
|
+
| Pattern | Severity | Confidence | Description |
|
|
146
|
+
|---------|----------|------------|-------------|
|
|
147
|
+
| `tabindex` value >0 (positive) | HIGH | definitive | WCAG 2.4.3 — breaks natural tab order |
|
|
148
|
+
| `tabindex="-1"` on natively interactive element (`<button>`, `<a href>`, `<input>`, etc.) | WARN | definitive | Removes from tab order |
|
|
149
|
+
| `outline: none` or `outline: 0` on focusable selector without `:focus-visible` replacement | HIGH | definitive | WCAG 2.4.7 |
|
|
150
|
+
| `onClick` / `onKeyDown` handler on `<div>` / `<span>` without `role="button"` + `tabindex="0"` + keydown handler for Enter/Space | HIGH | heuristic | WCAG 2.1.1 — not keyboard-accessible |
|
|
151
|
+
| No skip link (`<a href="#main">`, `<a href="#content">`) on page with navigation | WARN | heuristic | WCAG 2.4.1 Bypass Blocks |
|
|
152
|
+
| Potential keyboard trap: `event.preventDefault()` / `event.stopPropagation()` in keydown handler on modal/dialog without Escape handling | WARN | heuristic | WCAG 2.1.2 |
|
|
153
|
+
| Custom dropdown / combobox without `aria-expanded` + `aria-haspopup` + keyboard handlers | WARN | heuristic | WAI-ARIA Authoring Practices |
|
|
154
|
+
| `autofocus` on page load on non-critical input (distracts keyboard users, moves focus unexpectedly) | WARN | definitive | Confuses assistive tech |
|
|
155
|
+
| `contenteditable="true"` without `aria-label` / `aria-labelledby` | WARN | definitive | WCAG 4.1.2 |
|
|
156
|
+
|
|
157
|
+
### Category 4: Color, Contrast & Visual Cues
|
|
158
|
+
|
|
159
|
+
WCAG 1.4.1 (Use of Color), 1.4.3 (Contrast Minimum), 1.4.11 (Non-text Contrast).
|
|
160
|
+
|
|
161
|
+
**Static analysis limitation**: actual contrast ratios depend on the CSS cascade, custom properties, theme switching, and background images. The skill flags patterns where contrast is AT RISK; pair with runtime tools (axe-core, Lighthouse) for definitive measurement.
|
|
162
|
+
|
|
163
|
+
| Pattern | Severity | Confidence | Description |
|
|
164
|
+
|---------|----------|------------|-------------|
|
|
165
|
+
| Hardcoded foreground+background color pairs in CSS where computed contrast is <4.5:1 (normal) or <3:1 (large text) | WARN | heuristic | WCAG 1.4.3 — verify at runtime |
|
|
166
|
+
| Link inside body text without underline/border AND only `color` distinguishing it | WARN | heuristic | WCAG 1.4.1 — color-only signalling |
|
|
167
|
+
| Error/required field indicated only by red color (no icon, text, or shape) | WARN | heuristic | WCAG 1.4.1 |
|
|
168
|
+
| Required form field marked only with `*` character without `aria-required="true"` + text explanation | WARN | definitive | WCAG 1.4.1 + 3.3.2 |
|
|
169
|
+
| CSS uses `color: red`/`color: green` as sole signal (success vs error) | WARN | heuristic | WCAG 1.4.1 |
|
|
170
|
+
| Focus indicator with <3:1 contrast against background (heuristic from color values) | WARN | heuristic | WCAG 1.4.11 |
|
|
171
|
+
| Button/input border color with <3:1 contrast against adjacent color | WARN | heuristic | WCAG 1.4.11 |
|
|
172
|
+
| CSS `text-shadow`/`opacity` on body text reducing effective contrast | INFO | heuristic | May affect 1.4.3 |
|
|
173
|
+
|
|
174
|
+
### Category 5: Forms, Labels & Errors
|
|
175
|
+
|
|
176
|
+
WCAG 1.3.5 (Identify Input Purpose), 3.3.1 (Error Identification), 3.3.2 (Labels or Instructions), 3.3.3 (Error Suggestion), 4.1.2 (Name, Role, Value).
|
|
177
|
+
|
|
178
|
+
| Pattern | Severity | Confidence | Description |
|
|
179
|
+
|---------|----------|------------|-------------|
|
|
180
|
+
| `<input>` / `<select>` / `<textarea>` without `<label for="...">` AND without `aria-label` / `aria-labelledby` | HIGH | heuristic | WCAG 3.3.2, 4.1.2 |
|
|
181
|
+
| `<label>` without `for` attribute (implicit association only works if input is a child) | WARN | definitive | WCAG 3.3.2 |
|
|
182
|
+
| `<input type="email"/tel/name/password/address">` without `autocomplete` attribute | WARN | definitive | WCAG 1.3.5 |
|
|
183
|
+
| Missing `autocomplete="one-time-code"` on OTP input with `inputmode="numeric"` | INFO | definitive | Improves user experience |
|
|
184
|
+
| Radio / checkbox group without `<fieldset>` + `<legend>` | WARN | heuristic | WCAG 1.3.1 |
|
|
185
|
+
| Error messages displayed visually but not linked via `aria-describedby` to the input | WARN | heuristic | WCAG 3.3.1 |
|
|
186
|
+
| `required` attribute without accompanying `aria-required="true"` (belt-and-suspenders for assistive tech consistency) | INFO | definitive | WCAG 4.1.2 (modern SR handle `required` but legacy may not) |
|
|
187
|
+
| Error uses `role="alert"` without being updated dynamically (static alert on page load) | INFO | heuristic | WCAG 4.1.3 |
|
|
188
|
+
| Placeholder used as label (no visible label, only `placeholder`) | WARN | heuristic | WCAG 3.3.2 — placeholder disappears on focus |
|
|
189
|
+
| `<input type="email">` without `inputmode="email"` (mobile UX) | INFO | definitive | EN 301 549 mobile |
|
|
190
|
+
|
|
191
|
+
### Category 6: Media (Audio, Video, Embeds)
|
|
192
|
+
|
|
193
|
+
WCAG 1.2.1–1.2.5 (Captions, audio description, sign language), 1.4.2 (Audio Control).
|
|
194
|
+
|
|
195
|
+
**EAA is specifically strict about media** — video without captions is a common legal-risk finding.
|
|
196
|
+
|
|
197
|
+
| Pattern | Severity | Confidence | Description |
|
|
198
|
+
|---------|----------|------------|-------------|
|
|
199
|
+
| `<video>` without `<track kind="captions">` child (or `<track kind="subtitles">` for foreign-language) | HIGH | definitive | WCAG 1.2.2 — EAA legal risk |
|
|
200
|
+
| `<video>` without transcript link or `<track kind="descriptions">` | WARN | heuristic | WCAG 1.2.3 / 1.2.5 |
|
|
201
|
+
| `<audio>` without transcript link or `<track kind="captions">` | HIGH | definitive | WCAG 1.2.1 |
|
|
202
|
+
| `<video autoplay>` without `muted` | HIGH | definitive | WCAG 1.4.2 — auto-playing audio |
|
|
203
|
+
| `<video autoplay loop>` running >5 seconds without pause control | WARN | heuristic | WCAG 1.4.2, 2.2.2 |
|
|
204
|
+
| YouTube/Vimeo embed URL without `cc_load_policy=1` or equivalent CC parameter | INFO | definitive | Platform-dependent captioning |
|
|
205
|
+
| YouTube embed via `<iframe src="https://www.youtube.com/embed/...">` without accessibility enhancements | INFO | definitive | Note: platform controls most a11y |
|
|
206
|
+
| Live media without real-time caption indication | WARN | heuristic | WCAG 1.2.4 |
|
|
207
|
+
| Background video (hero section) without pause button in DOM | WARN | heuristic | WCAG 2.2.2 |
|
|
208
|
+
|
|
209
|
+
### Category 7: ARIA, Live Regions & Dynamic Content
|
|
210
|
+
|
|
211
|
+
WCAG 4.1.2 (Name, Role, Value), 4.1.3 (Status Messages).
|
|
212
|
+
|
|
213
|
+
| Pattern | Severity | Confidence | Description |
|
|
214
|
+
|---------|----------|------------|-------------|
|
|
215
|
+
| `aria-hidden="true"` on focusable element | HIGH | definitive | Creates orphaned focus — serious a11y bug |
|
|
216
|
+
| `role="button"` on native `<button>` (redundant ARIA) | WARN | definitive | ARIA Authoring: avoid redundant roles |
|
|
217
|
+
| `role="link"` on `<a href>` / `role="heading"` on `<h1–h6>` (redundant ARIA) | WARN | definitive | Same |
|
|
218
|
+
| Conflicting roles (`<button role="link">`, `<a role="button">`) | WARN | definitive | WAI-ARIA — wrong role |
|
|
219
|
+
| Custom toggle (disclosure, menu, accordion) without `aria-expanded` + `aria-controls` | WARN | heuristic | WAI-ARIA |
|
|
220
|
+
| `aria-labelledby` referencing non-existent ID | HIGH | heuristic | Broken reference |
|
|
221
|
+
| `aria-describedby` referencing non-existent ID | HIGH | heuristic | Broken reference |
|
|
222
|
+
| `aria-live` region without `role="status"` / `role="alert"` AND async updates in component (heuristic) | WARN | heuristic | WCAG 4.1.3 |
|
|
223
|
+
| Toast/notification component without `role="status"` or `role="alert"` | WARN | heuristic | WCAG 4.1.3 |
|
|
224
|
+
| Modal / dialog without `role="dialog"` + `aria-modal="true"` + focus trap | WARN | heuristic | WAI-ARIA Authoring Practices |
|
|
225
|
+
| Tabs without proper roles (`role="tablist"` + `role="tab"` + `role="tabpanel"`) | WARN | heuristic | WAI-ARIA Authoring Practices |
|
|
226
|
+
|
|
227
|
+
### Category 8: Motion, Target Size, Mobile & EAA Docs
|
|
228
|
+
|
|
229
|
+
#### 8a. Motion & Animation
|
|
230
|
+
|
|
231
|
+
WCAG 2.2.2 (Pause, Stop, Hide), 2.3.3 (Animation from Interactions — AAA but EAA-recommended).
|
|
232
|
+
|
|
233
|
+
| Pattern | Severity | Confidence | Description |
|
|
234
|
+
|---------|----------|------------|-------------|
|
|
235
|
+
| CSS animation / transition / transform without matching `@media (prefers-reduced-motion: reduce)` override | WARN | heuristic | WCAG 2.3.3 |
|
|
236
|
+
| JS animation library (GSAP, framer-motion, anime.js) without `matchMedia('(prefers-reduced-motion: reduce)')` check | WARN | heuristic | WCAG 2.3.3 |
|
|
237
|
+
| Parallax scrolling without opt-out | WARN | heuristic | WCAG 2.3.3 |
|
|
238
|
+
| Infinite animation (CSS `animation: name infinite`) on content element without pause control | WARN | heuristic | WCAG 2.2.2 |
|
|
239
|
+
| `<marquee>` / `<blink>` (deprecated) | HIGH | definitive | WCAG 2.2.2 |
|
|
240
|
+
|
|
241
|
+
#### 8b. Target Size (WCAG 2.2 Minimum AA 2.5.8 + EN 301 549)
|
|
242
|
+
|
|
243
|
+
| Pattern | Severity | Confidence | Description |
|
|
244
|
+
|---------|----------|------------|-------------|
|
|
245
|
+
| Interactive target with declared size <24×24 px (heuristic from CSS: `width`/`height`/`padding` on buttons/links/inputs) | WARN | heuristic | WCAG 2.2 2.5.8 / EN 301 549 |
|
|
246
|
+
| Touch-target spacing <8 px between adjacent interactive elements | INFO | heuristic | Best practice |
|
|
247
|
+
|
|
248
|
+
#### 8c. Viewport & Zoom
|
|
249
|
+
|
|
250
|
+
| Pattern | Severity | Confidence | Description |
|
|
251
|
+
|---------|----------|------------|-------------|
|
|
252
|
+
| `<meta name="viewport">` containing `user-scalable=no` / `user-scalable=0` | HIGH | definitive | WCAG 1.4.4 — blocks zoom |
|
|
253
|
+
| `<meta name="viewport">` with `maximum-scale=1` / `maximum-scale=1.0` | HIGH | definitive | WCAG 1.4.4 |
|
|
254
|
+
| Content rendered via `<img>` for text (text-as-image) | WARN | heuristic | WCAG 1.4.5 |
|
|
255
|
+
|
|
256
|
+
#### 8d. Mobile (React Native + Flutter)
|
|
257
|
+
|
|
258
|
+
EN 301 549 mobile chapter. Critical for EAA scope since consumer apps are in-scope.
|
|
259
|
+
|
|
260
|
+
| Pattern | Severity | Confidence | Description |
|
|
261
|
+
|---------|----------|------------|-------------|
|
|
262
|
+
| **React Native**: `<TouchableOpacity>` / `<TouchableHighlight>` / `<Pressable>` without `accessibilityLabel` | HIGH | definitive | EN 301 549 mobile |
|
|
263
|
+
| **React Native**: `<Image>` without `accessibilityLabel` or `accessible={false}` | WARN | heuristic | EN 301 549 |
|
|
264
|
+
| **React Native**: Missing `accessibilityRole` on custom components that behave as buttons/links | WARN | heuristic | EN 301 549 |
|
|
265
|
+
| **React Native**: `Alert.alert` for error flow without `AccessibilityInfo.announceForAccessibility` fallback | INFO | heuristic | — |
|
|
266
|
+
| **Flutter**: Interactive widget (`GestureDetector`, `InkWell`, `TextButton`, `IconButton`) without `Semantics()` wrapper or `semanticLabel` parameter | HIGH | definitive | EN 301 549 mobile |
|
|
267
|
+
| **Flutter**: `Image()` / `Image.asset()` / `Image.network()` without `semanticLabel` (or `excludeFromSemantics: true` for decorative) | WARN | definitive | EN 301 549 |
|
|
268
|
+
| **Flutter**: Missing `ExcludeSemantics` / `MergeSemantics` where child semantics conflict | INFO | heuristic | Semantics tree cleanup |
|
|
269
|
+
|
|
270
|
+
#### 8e. EAA Accessibility Documentation
|
|
271
|
+
|
|
272
|
+
**Activated by `--standard eaa`.** EAA Article 14 + member-state transpositions require consumer-facing services to publish an accessibility statement. Missing statement = HIGH legal finding.
|
|
273
|
+
|
|
274
|
+
| Pattern | Severity | Confidence | Description |
|
|
275
|
+
|---------|----------|------------|-------------|
|
|
276
|
+
| No route at any of: `/accessibility`, `/accessibility-statement`, `/a11y`, `/dostepnosc` (PL), `/barrierefreiheit` (DE), `/declaration-accessibilite` (FR), `/declaración-accesibilidad` (ES), `/dichiarazione-accessibilita` (IT), `/toegankelijkheidsverklaring` (NL) | HIGH | heuristic | EAA Article 14 |
|
|
277
|
+
| Footer / sitemap lacks link to accessibility statement | HIGH | heuristic | EAA visibility requirement |
|
|
278
|
+
| Accessibility statement present but missing required elements: (a) conformance level (WCAG/EN 301 549), (b) list of non-conformant content, (c) feedback mechanism, (d) enforcement procedure link | WARN | heuristic | Member-state template requirement |
|
|
279
|
+
| No contact mechanism (email / form) for accessibility feedback referenced in statement | WARN | heuristic | EAA Article 14 |
|
|
280
|
+
| `robots.txt` disallows `/accessibility*` path (accidentally blocks statement from crawlers + assistive tech) | WARN | definitive | Discoverability |
|
|
281
|
+
|
|
282
|
+
See: [reference/eaa-compliance.md](reference/eaa-compliance.md) for directive text, member-state deadlines, statement templates.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Output Format
|
|
287
|
+
|
|
288
|
+
```markdown
|
|
289
|
+
## Accessibility Validation Report
|
|
290
|
+
|
|
291
|
+
### Summary
|
|
292
|
+
| Metric | Value |
|
|
293
|
+
|--------|-------|
|
|
294
|
+
| Standard | wcag-2.1-aa / wcag-2.2-aa / en-301-549 / eaa |
|
|
295
|
+
| Scope | full / keyboard / contrast / forms / media / aria / motion / mobile / docs |
|
|
296
|
+
| Framework detected | next / nuxt / astro / ... / react-native / flutter / static |
|
|
297
|
+
| Files scanned | N |
|
|
298
|
+
| Public routes scanned | N |
|
|
299
|
+
| Accessibility statement | found / not-found |
|
|
300
|
+
| Findings: HIGH | N |
|
|
301
|
+
| Findings: WARN | N |
|
|
302
|
+
| Findings: INFO | N |
|
|
303
|
+
|
|
304
|
+
### Findings
|
|
305
|
+
|
|
306
|
+
#### [HIGH] src/components/VideoPlayer.tsx:42
|
|
307
|
+
Category: Media
|
|
308
|
+
Confidence: definitive
|
|
309
|
+
Pattern: `<video>` without `<track kind="captions">`
|
|
310
|
+
WCAG: 1.2.2 (Captions — Prerecorded, Level AA)
|
|
311
|
+
EAA: Article 4 (product/service accessibility requirements)
|
|
312
|
+
Fix: Add `<track kind="captions" src="/captions/en.vtt" srclang="en" label="English" default>`. If captions are unavailable, provide a transcript link.
|
|
313
|
+
See: reference/wcag-2-1-aa.md#guideline-12-time-based-media
|
|
314
|
+
|
|
315
|
+
#### [HIGH] public/index.html:8
|
|
316
|
+
Category: Viewport & Zoom
|
|
317
|
+
Confidence: definitive
|
|
318
|
+
Pattern: `<meta name="viewport" content="..., user-scalable=no">`
|
|
319
|
+
WCAG: 1.4.4 (Resize Text, Level AA)
|
|
320
|
+
Fix: Remove `user-scalable=no` and `maximum-scale=1` from the viewport meta — users must be able to zoom to 200%.
|
|
321
|
+
See: reference/wcag-2-1-aa.md#144-resize-text
|
|
322
|
+
|
|
323
|
+
#### [HIGH] src/routes.tsx:15
|
|
324
|
+
Category: EAA Accessibility Documentation
|
|
325
|
+
Confidence: heuristic
|
|
326
|
+
Pattern: No `/accessibility` / `/accessibility-statement` route detected; footer contains no a11y link
|
|
327
|
+
Standard: EAA Article 14 (mandatory accessibility statement)
|
|
328
|
+
Fix: Publish an accessibility statement conforming to your member-state template. Link it from the footer of every public page. See reference/eaa-compliance.md for template structure.
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Confidence values**:
|
|
332
|
+
- `definitive` — regex match against a known-bad pattern.
|
|
333
|
+
- `heuristic` — co-occurrence, absence, ordering, or derived inference (target size from CSS, contrast from hardcoded colors, ATF detection).
|
|
334
|
+
|
|
335
|
+
**Exit codes** (when `--output json`):
|
|
336
|
+
- `0` — no HIGH findings.
|
|
337
|
+
- `1` — one or more HIGH findings.
|
|
338
|
+
|
|
339
|
+
## Out of Scope (Static Analysis Cannot Detect)
|
|
340
|
+
|
|
341
|
+
The skill explicitly does NOT verify the following — pair with runtime tools:
|
|
342
|
+
|
|
343
|
+
- **Actual contrast ratios** under runtime CSS cascade, theme switching, custom properties (use `axe-core`, Lighthouse, or manual tooling).
|
|
344
|
+
- **Zoom / reflow behavior** at 200% / 400% (WCAG 1.4.10, 1.4.4) — requires rendering.
|
|
345
|
+
- **Screen reader announcement order and quality** (NVDA, JAWS, VoiceOver, TalkBack).
|
|
346
|
+
- **Cognitive accessibility** (WCAG 3.x is mostly process/content-driven, not pattern-matchable).
|
|
347
|
+
- **Actual keyboard trap behavior** — requires interaction.
|
|
348
|
+
- **Pronunciation / lang switches** at runtime.
|
|
349
|
+
- **Real-time caption accuracy**.
|
|
350
|
+
- **Usability / comprehension** — requires user studies.
|
|
351
|
+
|
|
352
|
+
For these, use: `axe-core`, `pa11y`, Lighthouse accessibility audit, manual assistive-tech testing, and user research with disabled participants.
|
|
353
|
+
|
|
354
|
+
## Rules
|
|
355
|
+
|
|
356
|
+
- **Read-only**: Never modify any files.
|
|
357
|
+
- **Framework-aware**: Detect framework first; apply correct pattern set.
|
|
358
|
+
- **Standards citation**: Every HIGH/WARN finding cites a WCAG success criterion (e.g., "1.3.1") or EN 301 549 clause.
|
|
359
|
+
- **Skip non-source files**: Binary files, lock files, vendored directories (`node_modules/`, `vendor/`, `dist/`, `build/`, `.next/`, `.nuxt/`, `.svelte-kit/`, `ios/Pods/`, `android/build/`, `.dart_tool/`).
|
|
360
|
+
- **No false confidence**: Label heuristic findings clearly. Color contrast and target size are ALWAYS heuristic in static analysis.
|
|
361
|
+
- **EAA docs category is LEGAL risk**: Missing accessibility statement when `--standard eaa` is selected is HIGH — this is a regulatory finding, not a code-quality suggestion.
|
|
362
|
+
- **No auto-fix**: A11y fixes often require design/content decisions that exceed pattern matching.
|
|
363
|
+
- **Don't flag missing ARIA when native semantics suffice**: Prefer native HTML elements; flag redundant ARIA, not absence when the native element is already there.
|
|
364
|
+
|
|
365
|
+
## Reference Documents
|
|
366
|
+
|
|
367
|
+
- [reference/wcag-2-1-aa.md](reference/wcag-2-1-aa.md) — All 50 Level A + AA success criteria with detection status (statically detectable vs runtime-only).
|
|
368
|
+
- [reference/wcag-2-2-aa.md](reference/wcag-2-2-aa.md) — 9 new WCAG 2.2 success criteria (2.4.11, 2.4.12, 2.4.13, 2.5.7, 2.5.8, 3.2.6, 3.3.7, 3.3.8, 3.3.9) with failure patterns, grep patterns, and framework notes.
|
|
369
|
+
- [reference/eaa-compliance.md](reference/eaa-compliance.md) — EU Directive 2019/882 articles, EN 301 549 v3.2.1 mapping, 28 June 2025 timeline, member-state transposition deltas, accessibility-statement templates.
|
|
370
|
+
- [reference/aria-patterns.md](reference/aria-patterns.md) — ARIA 1.2 Authoring Practices: landmarks, roles, states/properties, common anti-patterns, framework-specific helpers (`@react-aria`, `@angular/cdk/a11y`).
|
|
371
|
+
- [reference/mobile-eaa.md](reference/mobile-eaa.md) — EN 301 549 mobile chapter + React Native `AccessibilityInfo` / Flutter `Semantics()` patterns.
|
|
372
|
+
|
|
373
|
+
## Related Skills
|
|
374
|
+
|
|
375
|
+
- `/seo-validate` — SEO scanner; Category 9 covers a11y-for-SEO overlap only. For deep a11y compliance use `/a11y-validate`.
|
|
376
|
+
- `/cve-scan` — dependency vulnerability scanner.
|
|
377
|
+
- `/hipaa-validate` — HIPAA compliance scanner (similar pattern).
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# ARIA 1.2 Authoring Patterns Reference
|
|
2
|
+
|
|
3
|
+
Reference for `a11y-validate` Category 7. ARIA roles, states, properties, common anti-patterns, and framework-specific helpers.
|
|
4
|
+
|
|
5
|
+
Source: WAI-ARIA 1.2 W3C Recommendation (June 2023), ARIA Authoring Practices Guide (APG).
|
|
6
|
+
|
|
7
|
+
## First Rule of ARIA
|
|
8
|
+
|
|
9
|
+
**Don't use ARIA if native HTML does the job.** Every `role="button"` on a `<div>` is a sign of a missing `<button>`. Native elements come with keyboard handling, focus management, accessibility-tree exposure, and OS-level AT integration for free.
|
|
10
|
+
|
|
11
|
+
ARIA is for:
|
|
12
|
+
- Widgets native HTML doesn't provide (tablist, tree, combobox with rich popup).
|
|
13
|
+
- Dynamic states (`aria-expanded`, `aria-busy`, `aria-invalid`).
|
|
14
|
+
- Supplementing native semantics (`aria-label`, `aria-describedby`).
|
|
15
|
+
- Live regions for async updates (`aria-live`, `role="status"`).
|
|
16
|
+
|
|
17
|
+
## Landmark Roles
|
|
18
|
+
|
|
19
|
+
Semantic HTML5 elements have implicit landmark roles — use them first:
|
|
20
|
+
|
|
21
|
+
| HTML | Implicit role | Usage |
|
|
22
|
+
|------|--------------|-------|
|
|
23
|
+
| `<header>` | `banner` (when top-level child of body) | Site header |
|
|
24
|
+
| `<nav>` | `navigation` | Navigation regions |
|
|
25
|
+
| `<main>` | `main` | Primary content (exactly one per page) |
|
|
26
|
+
| `<aside>` | `complementary` | Sidebar, related content |
|
|
27
|
+
| `<footer>` | `contentinfo` (when top-level child of body) | Site footer |
|
|
28
|
+
| `<section>` with `aria-label` | `region` | Named section |
|
|
29
|
+
| `<form>` with `aria-label` | `form` | Named form region |
|
|
30
|
+
| `<search>` (HTML 2024) | `search` | Search region |
|
|
31
|
+
|
|
32
|
+
For pre-HTML5 codebases: `<div role="banner">`, `<div role="navigation">`, `<div role="main">`, etc. Flag these as UPGRADE opportunities.
|
|
33
|
+
|
|
34
|
+
## Widget Roles
|
|
35
|
+
|
|
36
|
+
| Role | Requires | Notes |
|
|
37
|
+
|------|---------|-------|
|
|
38
|
+
| `button` | Keyboard activation (Enter + Space), focus | Prefer `<button>` |
|
|
39
|
+
| `checkbox` | `aria-checked`, keyboard activation | Prefer `<input type="checkbox">` |
|
|
40
|
+
| `radio` + `radiogroup` | `aria-checked`, arrow-key navigation | Prefer `<input type="radio">` |
|
|
41
|
+
| `combobox` | `aria-expanded`, `aria-controls`, `aria-activedescendant`, arrow keys | Rich autocomplete; no native equivalent |
|
|
42
|
+
| `listbox` + `option` | `aria-selected`, arrow keys | Prefer `<select>` for simple cases |
|
|
43
|
+
| `menu` + `menuitem` | Arrow keys, Escape, focus trap | Application menus; NOT nav links |
|
|
44
|
+
| `tab` + `tablist` + `tabpanel` | `aria-selected`, `aria-controls`, arrow keys | Tabs |
|
|
45
|
+
| `tree` + `treeitem` | Arrow keys, `aria-expanded`, `aria-level` | File tree, org tree |
|
|
46
|
+
| `grid` + `gridcell` + `row` | Arrow keys | Interactive data grid |
|
|
47
|
+
| `dialog` / `alertdialog` | `aria-modal="true"`, focus trap, Escape | Prefer `<dialog>` (HTML5) |
|
|
48
|
+
| `tooltip` | Hovered/focused trigger | Not focusable itself |
|
|
49
|
+
| `progressbar` | `aria-valuenow`, `aria-valuemin`, `aria-valuemax` | Prefer `<progress>` |
|
|
50
|
+
| `slider` | `aria-valuenow`, arrow keys | Prefer `<input type="range">` |
|
|
51
|
+
| `switch` | `aria-checked` | Toggle (no native equivalent) |
|
|
52
|
+
|
|
53
|
+
## States and Properties
|
|
54
|
+
|
|
55
|
+
### Widget states (dynamic)
|
|
56
|
+
|
|
57
|
+
- `aria-checked` — checkbox/radio/switch state
|
|
58
|
+
- `aria-selected` — option/tab/cell selected
|
|
59
|
+
- `aria-expanded` — disclosure/combobox open
|
|
60
|
+
- `aria-pressed` — toggle button pressed
|
|
61
|
+
- `aria-disabled` — disabled (prefer native `disabled` where possible)
|
|
62
|
+
- `aria-hidden` — removed from AT tree (DO NOT use on focusable elements)
|
|
63
|
+
- `aria-invalid` — form input invalid
|
|
64
|
+
- `aria-busy` — content loading
|
|
65
|
+
- `aria-current` — current item in a set (page, step, date, location, time, true)
|
|
66
|
+
|
|
67
|
+
### Widget properties (static)
|
|
68
|
+
|
|
69
|
+
- `aria-label` — accessible name (overrides visible text)
|
|
70
|
+
- `aria-labelledby` — accessible name from other element(s) by ID
|
|
71
|
+
- `aria-describedby` — accessible description by ID
|
|
72
|
+
- `aria-controls` — element controlled by this (e.g., tab → tabpanel)
|
|
73
|
+
- `aria-owns` — parent/child in accessibility tree (rarely needed)
|
|
74
|
+
- `aria-haspopup` — indicates popup type: menu / listbox / tree / grid / dialog
|
|
75
|
+
- `aria-level` — heading/tree depth (integer)
|
|
76
|
+
- `aria-setsize` / `aria-posinset` — position in a set
|
|
77
|
+
|
|
78
|
+
### Live region properties
|
|
79
|
+
|
|
80
|
+
- `aria-live` — polite / assertive / off
|
|
81
|
+
- `aria-atomic` — read whole region or just changes
|
|
82
|
+
- `aria-relevant` — additions / removals / text / all (limited browser support)
|
|
83
|
+
|
|
84
|
+
Roles with implicit `aria-live`:
|
|
85
|
+
- `role="alert"` — `aria-live="assertive"` + `aria-atomic="true"`
|
|
86
|
+
- `role="status"` — `aria-live="polite"` + `aria-atomic="true"`
|
|
87
|
+
- `role="log"` — `aria-live="polite"`
|
|
88
|
+
- `role="timer"` — `aria-live="off"` (implicit)
|
|
89
|
+
- `role="marquee"` — `aria-live="off"` (implicit)
|
|
90
|
+
|
|
91
|
+
## Common Anti-Patterns (flagged by Category 7)
|
|
92
|
+
|
|
93
|
+
### 1. Redundant ARIA
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<!-- Bad: role duplicates native semantics -->
|
|
97
|
+
<button role="button">Submit</button>
|
|
98
|
+
<a href="/x" role="link">Link</a>
|
|
99
|
+
<h1 role="heading" aria-level="1">Title</h1>
|
|
100
|
+
<nav role="navigation">...</nav>
|
|
101
|
+
<main role="main">...</main>
|
|
102
|
+
|
|
103
|
+
<!-- Good -->
|
|
104
|
+
<button>Submit</button>
|
|
105
|
+
<a href="/x">Link</a>
|
|
106
|
+
<h1>Title</h1>
|
|
107
|
+
<nav>...</nav>
|
|
108
|
+
<main>...</main>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Conflicting roles
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<!-- Bad -->
|
|
115
|
+
<button role="link">Go to page</button>
|
|
116
|
+
<a role="button" href="javascript:void(0)" onclick="...">Click</a>
|
|
117
|
+
|
|
118
|
+
<!-- Good — match role to behavior -->
|
|
119
|
+
<a href="/x">Go to page</a>
|
|
120
|
+
<button onclick="...">Click</button>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. `aria-hidden` on focusable element
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<!-- Bad — keyboard user tabs into invisible content -->
|
|
127
|
+
<button aria-hidden="true">Skip</button>
|
|
128
|
+
|
|
129
|
+
<!-- Good — use inert (HTML 2024) or remove from DOM -->
|
|
130
|
+
<div inert>
|
|
131
|
+
<button>Skip</button>
|
|
132
|
+
</div>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. `tabindex="-1"` on native interactive
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<!-- Bad — removes from tab order for no reason -->
|
|
139
|
+
<button tabindex="-1">Submit</button>
|
|
140
|
+
|
|
141
|
+
<!-- Good: only use tabindex=-1 on elements you focus programmatically (modal root, error summary) -->
|
|
142
|
+
<div role="dialog" tabindex="-1" aria-modal="true" ref={modalRef}>
|
|
143
|
+
...
|
|
144
|
+
</div>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 5. Missing `aria-label` for icon buttons
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<!-- Bad — screen reader announces "button" with no name -->
|
|
151
|
+
<button><svg aria-hidden="true"><!-- icon --></svg></button>
|
|
152
|
+
|
|
153
|
+
<!-- Good -->
|
|
154
|
+
<button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 6. Broken `aria-labelledby` / `aria-describedby` references
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<!-- Bad — no element with id="hint" exists -->
|
|
161
|
+
<input aria-describedby="hint">
|
|
162
|
+
|
|
163
|
+
<!-- Good -->
|
|
164
|
+
<input aria-describedby="email-hint">
|
|
165
|
+
<span id="email-hint">We'll never share your email.</span>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 7. `role="presentation"` on interactive
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<!-- Bad — strips button semantics -->
|
|
172
|
+
<button role="presentation">Submit</button>
|
|
173
|
+
|
|
174
|
+
<!-- Good — role=presentation only on purely visual containers -->
|
|
175
|
+
<div role="presentation">
|
|
176
|
+
<img src="decorative.jpg" alt="">
|
|
177
|
+
</div>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## WAI-ARIA Authoring Practices Guide (APG) Widgets
|
|
181
|
+
|
|
182
|
+
The APG provides ready-made keyboard and ARIA patterns for common widgets:
|
|
183
|
+
|
|
184
|
+
- **Accordion**: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/
|
|
185
|
+
- **Alert**: https://www.w3.org/WAI/ARIA/apg/patterns/alert/
|
|
186
|
+
- **Alert and Message Dialogs**: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
|
|
187
|
+
- **Breadcrumb**: https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/
|
|
188
|
+
- **Button**: https://www.w3.org/WAI/ARIA/apg/patterns/button/
|
|
189
|
+
- **Carousel**: https://www.w3.org/WAI/ARIA/apg/patterns/carousel/
|
|
190
|
+
- **Checkbox**: https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/
|
|
191
|
+
- **Combobox**: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
|
|
192
|
+
- **Dialog (modal)**: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
|
|
193
|
+
- **Disclosure (Show/Hide)**: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
|
|
194
|
+
- **Feed**: https://www.w3.org/WAI/ARIA/apg/patterns/feed/
|
|
195
|
+
- **Grid**: https://www.w3.org/WAI/ARIA/apg/patterns/grid/
|
|
196
|
+
- **Landmarks**: https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/
|
|
197
|
+
- **Link**: https://www.w3.org/WAI/ARIA/apg/patterns/link/
|
|
198
|
+
- **Listbox**: https://www.w3.org/WAI/ARIA/apg/patterns/listbox/
|
|
199
|
+
- **Menu / Menubar**: https://www.w3.org/WAI/ARIA/apg/patterns/menubar/
|
|
200
|
+
- **Menu Button**: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/
|
|
201
|
+
- **Radio Group**: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
|
|
202
|
+
- **Slider**: https://www.w3.org/WAI/ARIA/apg/patterns/slider/
|
|
203
|
+
- **Slider (Multi-thumb)**: https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/
|
|
204
|
+
- **Spinbutton**: https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
|
|
205
|
+
- **Switch**: https://www.w3.org/WAI/ARIA/apg/patterns/switch/
|
|
206
|
+
- **Tabs**: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
|
|
207
|
+
- **Toolbar**: https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/
|
|
208
|
+
- **Tooltip**: https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
|
|
209
|
+
- **Tree View**: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
|
|
210
|
+
- **Treegrid**: https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/
|
|
211
|
+
- **Window Splitter**: https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/
|
|
212
|
+
|
|
213
|
+
## Framework-Specific A11y Helpers
|
|
214
|
+
|
|
215
|
+
Use these libraries instead of hand-rolling ARIA:
|
|
216
|
+
|
|
217
|
+
### React
|
|
218
|
+
|
|
219
|
+
- **React Aria** (Adobe): https://react-spectrum.adobe.com/react-aria/ — unstyled hooks with correct ARIA + keyboard.
|
|
220
|
+
- **Radix UI**: https://www.radix-ui.com/ — primitives with built-in a11y.
|
|
221
|
+
- **Reach UI** (deprecated but functional): https://reach.tech/
|
|
222
|
+
- **Headless UI** (Tailwind): https://headlessui.com/
|
|
223
|
+
- **ARIA Kit**: https://ariakit.org/
|
|
224
|
+
- `eslint-plugin-jsx-a11y` — lint a11y violations in JSX.
|
|
225
|
+
|
|
226
|
+
### Angular
|
|
227
|
+
|
|
228
|
+
- **Angular CDK a11y**: https://material.angular.io/cdk/a11y/ — `FocusTrap`, `LiveAnnouncer`, `FocusMonitor`, `ListKeyManager`.
|
|
229
|
+
- `@angular-eslint/eslint-plugin-template` — includes a11y rules.
|
|
230
|
+
|
|
231
|
+
### Vue
|
|
232
|
+
|
|
233
|
+
- **@headlessui/vue** — same as React version.
|
|
234
|
+
- **Vue a11y plugins**: `vue-accessibility`, `vuelidate-a11y`.
|
|
235
|
+
- `eslint-plugin-vuejs-accessibility`.
|
|
236
|
+
|
|
237
|
+
### Svelte / SvelteKit
|
|
238
|
+
|
|
239
|
+
- Svelte has **built-in a11y warnings** in `.svelte` templates (`a11y-missing-attribute`, etc.).
|
|
240
|
+
- `eslint-plugin-svelte` with `--rules 'svelte/a11y-*'`.
|
|
241
|
+
|
|
242
|
+
### Astro
|
|
243
|
+
|
|
244
|
+
- `@astrojs/check` — type + a11y checks for `.astro` files.
|
|
245
|
+
|
|
246
|
+
### Mobile
|
|
247
|
+
|
|
248
|
+
- **React Native**: `AccessibilityInfo` API, `accessibilityLabel`, `accessibilityRole`, `accessibilityState` props.
|
|
249
|
+
- **Flutter**: `Semantics()` widget, `MergeSemantics`, `ExcludeSemantics`, `semanticLabel` on most widgets.
|
|
250
|
+
|
|
251
|
+
See [mobile-eaa.md](mobile-eaa.md) for mobile-specific patterns.
|
|
252
|
+
|
|
253
|
+
## References
|
|
254
|
+
|
|
255
|
+
- WAI-ARIA 1.2 (W3C Recommendation): https://www.w3.org/TR/wai-aria-1.2/
|
|
256
|
+
- ARIA Authoring Practices Guide: https://www.w3.org/WAI/ARIA/apg/
|
|
257
|
+
- ARIA in HTML (how ARIA interacts with HTML): https://www.w3.org/TR/html-aria/
|
|
258
|
+
- Using ARIA (W3C): https://www.w3.org/TR/using-aria/
|
|
259
|
+
- MDN ARIA reference: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA
|