@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.
Files changed (37) hide show
  1. package/AGENTS.md +32 -19
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +11 -11
  4. package/app/.claude-plugin/plugin.json +1 -1
  5. package/app/agents/code-reviewer.md +6 -7
  6. package/app/agents/frontend-specialist.md +33 -2
  7. package/app/agents/seo-specialist.md +1 -1
  8. package/app/personas/frontend-lead.md +48 -5
  9. package/app/skills/a11y-validate/SKILL.md +377 -0
  10. package/app/skills/a11y-validate/reference/aria-patterns.md +259 -0
  11. package/app/skills/a11y-validate/reference/eaa-compliance.md +252 -0
  12. package/app/skills/a11y-validate/reference/mobile-eaa.md +329 -0
  13. package/app/skills/a11y-validate/reference/wcag-2-1-aa.md +285 -0
  14. package/app/skills/a11y-validate/reference/wcag-2-2-aa.md +221 -0
  15. package/app/skills/a11y-validate/scripts/a11y-scanner.py +639 -0
  16. package/app/skills/clean-code/reference/python.md +3 -3
  17. package/app/skills/design-engineering/SKILL.md +2 -5
  18. package/app/skills/review/SKILL.md +30 -6
  19. package/app/skills/seo-validate/SKILL.md +460 -0
  20. package/app/skills/seo-validate/reference/core-web-vitals.md +445 -0
  21. package/app/skills/seo-validate/reference/geo-aeo-patterns.md +259 -0
  22. package/app/skills/seo-validate/reference/geo-guidelines.md +248 -0
  23. package/app/skills/seo-validate/reference/schema-types.md +465 -0
  24. package/app/skills/seo-validate/reference/spa-ssg-patterns.md +351 -0
  25. package/app/skills/seo-validate/reference/w3c-guidelines.md +289 -0
  26. package/app/skills/seo-validate/scripts/seo-scanner.py +549 -0
  27. package/bin/ai-toolkit.js +4 -4
  28. package/kb/reference/architecture-overview.md +1 -1
  29. package/kb/reference/comparison.md +1 -1
  30. package/kb/reference/skills-catalog.md +3 -1
  31. package/llms-full.txt +10 -6
  32. package/manifest.json +3 -3
  33. package/package.json +2 -2
  34. package/scripts/config_cli.py +4 -10
  35. package/scripts/doctor.py +3 -3
  36. package/scripts/install_steps/ai_tools.py +1 -1
  37. 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