a11y-loop 0.1.2 → 0.2.1
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/.claude-plugin/marketplace.json +40 -0
- package/.claude-plugin/plugin.json +25 -0
- package/README.md +149 -13
- package/commands/a11y-plan.md +47 -0
- package/hooks/hooks.json +17 -0
- package/hooks/plan-gate.mjs +555 -0
- package/package.json +7 -2
- package/skill/a11y-loop/SKILL.md +92 -27
- package/skill/a11y-loop/evals/evals.json +46 -0
- package/skill/a11y-loop/evals/trigger-evals.json +15 -1
- package/skill/a11y-loop/references/ai-failure-modes.md +1 -0
- package/skill/a11y-loop/references/apg-patterns.md +1 -0
- package/skill/a11y-loop/references/manual-testing.md +1 -0
- package/skill/a11y-loop/references/plan-phase.md +365 -0
- package/skill/a11y-loop/references/wcag22-quick-ref.md +1 -0
package/skill/a11y-loop/SKILL.md
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: a11y-loop
|
|
3
3
|
description: >-
|
|
4
|
-
Make
|
|
5
|
-
browser instead of assuming. Use
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
navigation,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
Make accessibility a decision in the plan, accessible markup the default when
|
|
5
|
+
writing UI, then verify it in a real browser instead of assuming. Use when
|
|
6
|
+
planning, scoping or designing anything with a user interface — a new app or
|
|
7
|
+
page, a design system, a component library choice, a brand palette, a feature
|
|
8
|
+
roadmap — and whenever generating or modifying user interface code (HTML,
|
|
9
|
+
JSX/TSX, Vue, Svelte, Astro, CSS): forms, modals, menus, tabs, tables,
|
|
10
|
+
navigation, pricing pages, dark mode, colors. Also whenever the user asks for
|
|
11
|
+
an accessibility audit, an a11y check or fix, a WCAG review, a contrast fix,
|
|
12
|
+
or help with screen readers, keyboard navigation, focus order, ARIA, alt
|
|
13
|
+
text. Supplies plan-phase rules (conformance target, per-component acceptance
|
|
14
|
+
criteria, product decisions that foreclose accessibility, color-token and
|
|
15
|
+
structure planning), standing generation rules (semantic HTML first, ARIA
|
|
16
|
+
discipline, APG keyboard contracts, labels, focus visibility, AA contrast in
|
|
17
|
+
light and dark, reduced motion, 24x24 targets), a mandatory
|
|
18
|
+
audit-fix-re-audit loop driven by the `a11y-loop` CLI (axe-core in Chromium
|
|
19
|
+
across default, dark, forced-colors, reduced-motion and 320px passes), and
|
|
20
|
+
honest reporting of what automation cannot judge. Keywords: accessibility,
|
|
21
|
+
a11y, WCAG 2.2 AA, ARIA, axe-core, contrast ratio, screen reader. Not for
|
|
22
|
+
backend-only work with no UI.
|
|
18
23
|
license: MIT
|
|
19
24
|
compatibility: >-
|
|
20
25
|
Verification needs Node.js >= 20 and Playwright Chromium. Install with
|
|
@@ -23,24 +28,17 @@ compatibility: >-
|
|
|
23
28
|
The generation and honesty rules apply with or without the CLI; every step
|
|
24
29
|
that says "audit" requires it.
|
|
25
30
|
metadata:
|
|
26
|
-
"a11y-loop/version": "0.1
|
|
27
|
-
paths:
|
|
28
|
-
- "**/*.html"
|
|
29
|
-
- "**/*.tsx"
|
|
30
|
-
- "**/*.jsx"
|
|
31
|
-
- "**/*.vue"
|
|
32
|
-
- "**/*.svelte"
|
|
33
|
-
- "**/*.astro"
|
|
34
|
-
- "**/*.css"
|
|
31
|
+
"a11y-loop/version": "0.2.1"
|
|
35
32
|
allowed-tools: 'Bash(a11y-loop *) Bash(npx a11y-loop *) Bash(node ${CLAUDE_SKILL_DIR}/../../src/cli.js *)'
|
|
36
33
|
---
|
|
37
34
|
|
|
38
35
|
# a11y-loop
|
|
39
36
|
|
|
40
|
-
These are standing instructions. **For the remainder of this session
|
|
41
|
-
you write or modify UI code
|
|
42
|
-
the work done, and speak about the result only
|
|
43
|
-
They stay in force across turns — you do not need
|
|
37
|
+
These are standing instructions. **For the remainder of this session: when you
|
|
38
|
+
are planning UI work apply §0, when you write or modify UI code apply §1 as you
|
|
39
|
+
write it, run §2 before you call the work done, and speak about the result only
|
|
40
|
+
in the terms allowed by §3.** They stay in force across turns — you do not need
|
|
41
|
+
to be reminded.
|
|
44
42
|
|
|
45
43
|
Prompting alone does not work. UIs generated from accessibility-oriented
|
|
46
44
|
prompts measure *slightly worse* than accessibility-agnostic ones (W4A'25,
|
|
@@ -84,6 +82,72 @@ stable `fingerprint`, a `wcag` block (`sc`, `name`, `level`, `minVersion`,
|
|
|
84
82
|
|
|
85
83
|
---
|
|
86
84
|
|
|
85
|
+
## §0 Plan rules
|
|
86
|
+
|
|
87
|
+
Apply while scoping, designing, or estimating — before any code exists.
|
|
88
|
+
|
|
89
|
+
**Set the conformance target first and write it down.** Default to **WCAG 2.2
|
|
90
|
+
Level AA**: 2.2 is backward compatible, so it satisfies every major jurisdiction
|
|
91
|
+
at once — NZ Standard 1.2 and UK public sector at 2.2 AA, EU EN 301 549 v3.2.1
|
|
92
|
+
and US ADA Title II at 2.1 AA, US Section 508 at 2.0 AA. If the user's
|
|
93
|
+
jurisdiction, sector, or contract demands otherwise, ask once and record the
|
|
94
|
+
answer. Version table and sources: [references/plan-phase.md](references/plan-phase.md).
|
|
95
|
+
|
|
96
|
+
**Write accessibility as plan items, never as a final "accessibility pass."**
|
|
97
|
+
Each UI component in the plan carries its success criteria and its keyboard
|
|
98
|
+
contract alongside the rest of its scope. A plan whose last step is "then run an
|
|
99
|
+
accessibility audit" is exactly the pattern this skill exists to eliminate — it
|
|
100
|
+
defers the decisions §1 depends on until changing them costs a rewrite.
|
|
101
|
+
|
|
102
|
+
**Name the decisions that foreclose accessibility while they are still free to
|
|
103
|
+
change**, and propose the alternative in the same breath: drag-only reordering
|
|
104
|
+
(SC 2.5.7 — add move up/down or a "move to" menu), hover-only menus (SC 1.4.13 —
|
|
105
|
+
open on click, dismissible and hoverable), infinite scroll with no pagination
|
|
106
|
+
fallback, canvas/WebGL data with no DOM or table equivalent, timed flows (SC
|
|
107
|
+
2.2.1), autoplaying media (SC 1.4.2), CAPTCHA (SC 1.1.1 + 3.3.8 — a
|
|
108
|
+
non-cognitive path alongside it), and any custom widget where a native element
|
|
109
|
+
or an APG pattern would serve. Each is a product decision, not a CSS bug; after
|
|
110
|
+
launch it is re-architecture.
|
|
111
|
+
|
|
112
|
+
**Fix the color system before the components exist.** Run
|
|
113
|
+
`A11Y contrast <fg> <bg> --fix` on every pair the design intends — body, muted,
|
|
114
|
+
link, error, disabled, focus ring, control border — in **light and dark**, and
|
|
115
|
+
let the passing values become the tokens. Unlike §2 it needs no browser, no
|
|
116
|
+
render, and no code, so it is genuinely usable at plan time; low-contrast text
|
|
117
|
+
is the most common failure in the wild (83.9% of pages) and the cheapest to
|
|
118
|
+
never introduce.
|
|
119
|
+
|
|
120
|
+
**Vet a component library before adopting it**, not after — checklist in
|
|
121
|
+
[references/plan-phase.md](references/plan-phase.md).
|
|
122
|
+
|
|
123
|
+
**Decide the structure once**, in the plan: heading outline, landmark map, focus
|
|
124
|
+
order and keyboard map, skip links, and where focus lands on an SPA route
|
|
125
|
+
change. Left to per-component decisions later, these always drift.
|
|
126
|
+
|
|
127
|
+
**Plan the verification too.** List the states that will need an `--interact`
|
|
128
|
+
module — every modal, error state, route, expanded menu, and async list is a
|
|
129
|
+
state — say where the audit gates sit (per component, before merge, in CI), and
|
|
130
|
+
budget the manual testing §3 requires. It is neither optional nor free.
|
|
131
|
+
|
|
132
|
+
**Before you present a plan that touches UI, check it:** target written down?
|
|
133
|
+
every component carrying its criteria and keyboard contract? foreclosing
|
|
134
|
+
decisions named with alternatives? color pairs run through `contrast --fix` in
|
|
135
|
+
both modes? structure decided? `--interact` states listed? manual budget stated?
|
|
136
|
+
Then include this section in the plan, in this shape:
|
|
137
|
+
|
|
138
|
+
```markdown
|
|
139
|
+
### Accessibility
|
|
140
|
+
- **Target:** WCAG 2.2 Level AA — <rationale / jurisdiction>
|
|
141
|
+
- **Per-component criteria:** <component> → <SC list> + <keyboard contract source>
|
|
142
|
+
- **Foreclosing decisions:** <none reviewed | list + alternatives>
|
|
143
|
+
- **Color tokens:** <pairs verified with contrast --fix, light + dark>
|
|
144
|
+
- **Structure:** <heading outline / landmarks / focus order>
|
|
145
|
+
- **Verification:** <states needing --interact | where the audit gate sits>
|
|
146
|
+
- **Manual budget:** <what automation cannot judge here>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
87
151
|
## §1 Generation rules
|
|
88
152
|
|
|
89
153
|
Apply while writing, not afterwards.
|
|
@@ -326,6 +390,7 @@ Read on demand; none of it costs context until you open it.
|
|
|
326
390
|
|
|
327
391
|
| File | Use it when |
|
|
328
392
|
|---|---|
|
|
393
|
+
| [references/plan-phase.md](references/plan-phase.md) | You are planning, scoping, or estimating UI work: picking a conformance target, writing per-component acceptance criteria, reviewing a decision that might foreclose accessibility, planning color tokens or page structure, or choosing a component library |
|
|
329
394
|
| [references/wcag22-quick-ref.md](references/wcag22-quick-ref.md) | You need the exact SC number, level, contrast threshold, or what is new in WCAG 2.2 |
|
|
330
395
|
| [references/apg-patterns.md](references/apg-patterns.md) | You are building a dialog, tabs, accordion, menu, combobox, disclosure, radio group, switch, tooltip, or listbox |
|
|
331
396
|
| [references/ai-failure-modes.md](references/ai-failure-modes.md) | Before saving generated UI code, and when a finding needs a wrong-vs-right example |
|
|
@@ -163,6 +163,52 @@
|
|
|
163
163
|
"No accessibility guidance, audit, or checklist appears in the response",
|
|
164
164
|
"No other files were modified"
|
|
165
165
|
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": 13,
|
|
169
|
+
"should_trigger": true,
|
|
170
|
+
"prompt": "We're adding a team management page to the admin app: an invite form, a members table with per-row actions, and a confirmation modal for role changes. Before you write any code, give me the implementation plan.",
|
|
171
|
+
"expected_output": "A plan carrying a named conformance target, per-component success criteria, and the states that will be audited — decided at plan time, with no conformance claim attached to the outcome.",
|
|
172
|
+
"expectations": [
|
|
173
|
+
"The plan contains a dedicated Accessibility section rather than accessibility surfacing only as a final 'add ARIA' cleanup step",
|
|
174
|
+
"A target conformance level is stated explicitly (for example WCAG 2.2 Level AA) instead of being left implicit",
|
|
175
|
+
"Each planned component carries its own acceptance criteria cited by success criterion — for example the members table against SC 1.3.1, the invite form against SC 3.3.2 and 4.1.2, the confirmation modal against the APG dialog contract",
|
|
176
|
+
"The plan names the interactive states that will be driven and audited (modal open, invite form validation error, empty members table) rather than assuming a load-time scan covers them",
|
|
177
|
+
"Verification is scheduled as the a11y-loop audit-fix-re-audit loop at a named point in the build order, not deferred to an unscoped 'accessibility pass' at the end",
|
|
178
|
+
"The plan does not claim the resulting page will be accessible, compliant, or meet WCAG — it commits to a target and a check, not to an outcome",
|
|
179
|
+
"The plan was produced before any component code was written, as asked"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": 14,
|
|
184
|
+
"should_trigger": true,
|
|
185
|
+
"prompt": "Here's the shape of the new dashboard: users drag the cards around to reorder their own layout, and the widget menu opens on hover. Sketch me the build order.",
|
|
186
|
+
"expected_output": "The drag-only reordering and the hover-only menu identified as decisions that foreclose accessibility, each cited and each given a concrete alternative folded into the plan itself.",
|
|
187
|
+
"expectations": [
|
|
188
|
+
"Drag-to-reorder is flagged as needing a single-pointer alternative, citing SC 2.5.7 Dragging Movements (Level AA) by number",
|
|
189
|
+
"A concrete alternative is specified — for example Move up / Move down buttons on each card, or a position field in a card menu — rather than recorded as a TODO, a follow-up ticket, or a 'we should also consider' aside",
|
|
190
|
+
"The alternative sits in the same build step as the drag interaction, so the plan cannot ship the drag behavior on its own",
|
|
191
|
+
"The hover-opened widget menu is flagged against SC 1.4.13 Content on Hover or Focus, with click or focus activation, or dismissible-hoverable-persistent behavior, specified in the plan",
|
|
192
|
+
"Both flags are raised while planning, before any component was written or audited",
|
|
193
|
+
"The stated interactions are not simply accepted and built as described",
|
|
194
|
+
"No conformance claim is made about the planned dashboard"
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": 15,
|
|
199
|
+
"should_trigger": true,
|
|
200
|
+
"prompt": "Before we start building I want to lock the palette for the new app: brand blue #4A90D9, #6B7280 grey for secondary text, white surfaces in light mode and #111827 in dark. Does this work?",
|
|
201
|
+
"expected_output": "Every stated pair measured through a11y-loop contrast in both light and dark, with real ratios and CLI-supplied replacements, while the palette is still a decision and no component exists.",
|
|
202
|
+
"expectations": [
|
|
203
|
+
"a11y-loop contrast was actually executed for the proposed pairs rather than the ratios being estimated or recalled",
|
|
204
|
+
"The light-mode pairs and the dark-mode pairs were each measured separately; the dark values were not assumed to inherit from the light theme",
|
|
205
|
+
"A concrete measured ratio is reported per pair with a pass or fail against the applicable threshold (4.5:1 normal text, 3:1 large text and UI boundaries)",
|
|
206
|
+
"Failing pairs receive replacement hex values taken from a11y-loop contrast --fix output or the report suggestions, not invented",
|
|
207
|
+
"The measurement happened at palette-decision time — no component or page was built and audited first in order to discover the problem",
|
|
208
|
+
"Replacements are expressed as design tokens for the light and dark themes rather than as per-element overrides",
|
|
209
|
+
"SC 1.4.3 Contrast (Minimum) is cited, and SC 1.4.11 Non-text Contrast where UI boundaries are involved",
|
|
210
|
+
"The response does not claim the palette is accessible or WCAG compliant"
|
|
211
|
+
]
|
|
166
212
|
}
|
|
167
213
|
]
|
|
168
214
|
}
|
|
@@ -11,10 +11,24 @@
|
|
|
11
11
|
{ "query": "Is this modal usable with a screen reader and the keyboard alone?", "should_trigger": true },
|
|
12
12
|
{ "query": "Check whether our color palette meets WCAG AA contrast", "should_trigger": true },
|
|
13
13
|
{ "query": "I fixed the contrast issues you found — did it work, and did I break anything?", "should_trigger": true },
|
|
14
|
+
{ "query": "We're kicking off an internal analytics dashboard next sprint — let's talk through the approach and roadmap before writing anything", "should_trigger": true },
|
|
15
|
+
{ "query": "I want to build a design system for our product. What should go in the first release?", "should_trigger": true },
|
|
16
|
+
{ "query": "Radix vs Headless UI vs MUI vs shadcn for a new React app — which should we pick and why?", "should_trigger": true },
|
|
17
|
+
{ "query": "Help me pick a brand palette for the marketing site: a primary, an accent, a neutral ramp, and a dark theme", "should_trigger": true },
|
|
18
|
+
{ "query": "Scoping the new onboarding flow: a multi-step form, a plan comparison table and a confirmation modal. What's the plan?", "should_trigger": true },
|
|
19
|
+
{ "query": "We're rewriting the frontend from jQuery to Svelte this quarter — draft me a migration roadmap", "should_trigger": true },
|
|
20
|
+
{ "query": "For the dashboard I want users to drag the cards around to reorder their layout, and the activity feed to infinite scroll. Sound good?", "should_trigger": true },
|
|
21
|
+
{ "query": "Planning checkout: a 10-minute reservation timer, a hover mega-menu for categories, and the spend charts drawn on canvas", "should_trigger": true },
|
|
22
|
+
{ "query": "What's the best way to structure a settings page with tabs and a pile of toggles? Just thinking out loud, no code yet", "should_trigger": true },
|
|
14
23
|
{ "query": "Refactor the argument parsing in bin/deploy.js to use node:util parseArgs", "should_trigger": false },
|
|
15
24
|
{ "query": "This query times out, rewrite the correlated subquery as a window function", "should_trigger": false },
|
|
16
25
|
{ "query": "There's a typo in README.md, 'recieve' should be 'receive'", "should_trigger": false },
|
|
17
26
|
{ "query": "Add retry with exponential backoff to the S3 upload in workers/ingest.js", "should_trigger": false },
|
|
18
27
|
{ "query": "Write a GitHub Actions workflow that runs the unit tests on push", "should_trigger": false },
|
|
19
|
-
{ "query": "Why is my Express middleware running twice per request?", "should_trigger": false }
|
|
28
|
+
{ "query": "Why is my Express middleware running twice per request?", "should_trigger": false },
|
|
29
|
+
{ "query": "Let's design the REST API for the billing service — endpoints, auth, pagination and rate limits", "should_trigger": false },
|
|
30
|
+
{ "query": "I'm planning a CLI tool for bulk-renaming media files. What should the command structure be?", "should_trigger": false },
|
|
31
|
+
{ "query": "Plan the migration that splits the users table into users and user_profiles with zero downtime", "should_trigger": false },
|
|
32
|
+
{ "query": "Help me plan our CI setup: matrix builds, dependency caching and a release workflow on tags", "should_trigger": false },
|
|
33
|
+
{ "query": "Design the ETL pipeline: Kafka into a staging table, dbt models on top, nightly reconciliation job", "should_trigger": false }
|
|
20
34
|
]
|
|
@@ -269,4 +269,5 @@ Then run the audit. This list is the setup, not the verification.
|
|
|
269
269
|
|
|
270
270
|
## Related
|
|
271
271
|
|
|
272
|
+
[plan-phase.md](plan-phase.md) (the decisions that cause these before any code) ·
|
|
272
273
|
[wcag22-quick-ref.md](wcag22-quick-ref.md) · [apg-patterns.md](apg-patterns.md) · [manual-testing.md](manual-testing.md)
|
|
@@ -260,5 +260,6 @@ not: focus and selection are independent, and `Space` commits.
|
|
|
260
260
|
|
|
261
261
|
## Related
|
|
262
262
|
|
|
263
|
+
[plan-phase.md](plan-phase.md) (choosing the pattern before you build it) ·
|
|
263
264
|
[wcag22-quick-ref.md](wcag22-quick-ref.md) (the criteria behind these contracts) ·
|
|
264
265
|
[ai-failure-modes.md](ai-failure-modes.md) · [manual-testing.md](manual-testing.md)
|
|
@@ -219,6 +219,7 @@ also just untrue: what was checked is what was checked.
|
|
|
219
219
|
|
|
220
220
|
## Related
|
|
221
221
|
|
|
222
|
+
- [plan-phase.md](plan-phase.md) — budgeting this work before the code exists
|
|
222
223
|
- [wcag22-quick-ref.md](wcag22-quick-ref.md) — the 55 criteria and contrast thresholds
|
|
223
224
|
- [apg-patterns.md](apg-patterns.md) — keyboard contracts to verify by hand
|
|
224
225
|
- [ai-failure-modes.md](ai-failure-modes.md) — what to look for in generated code
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# Deciding accessibility before the code exists
|
|
2
|
+
|
|
3
|
+
§1 governs markup and §2 governs verification. Neither can reach a decision that
|
|
4
|
+
was made in a product spec. This file is the detail behind §0: the phase before
|
|
5
|
+
both, when the artifact is a plan, a roadmap, a ticket, a design review, or an
|
|
6
|
+
estimate.
|
|
7
|
+
|
|
8
|
+
**19 of the 55 WCAG 2.2 A/AA criteria are settled by product and design choices
|
|
9
|
+
rather than by markup.** The list, so you can check it against
|
|
10
|
+
[wcag22-quick-ref.md](wcag22-quick-ref.md) rather than take it on trust:
|
|
11
|
+
|
|
12
|
+
| | |
|
|
13
|
+
|---|---|
|
|
14
|
+
| **Layout and visual** | 1.3.4 Orientation · 1.4.3 Contrast · 1.4.5 Images of Text · 1.4.10 Reflow · 1.4.11 Non-text Contrast · 2.5.8 Target Size |
|
|
15
|
+
| **Interaction model** | 1.4.13 Content on Hover or Focus · 2.5.1 Pointer Gestures · 2.5.7 Dragging Movements |
|
|
16
|
+
| **Timing and motion** | 2.2.1 Timing Adjustable · 2.2.2 Pause, Stop, Hide |
|
|
17
|
+
| **Information architecture** | 2.4.1 Bypass Blocks · 2.4.5 Multiple Ways · 3.2.3 Consistent Navigation · 3.2.4 Consistent Identification · 3.2.6 Consistent Help |
|
|
18
|
+
| **Flows** | 3.3.4 Error Prevention · 3.3.7 Redundant Entry · 3.3.8 Accessible Authentication |
|
|
19
|
+
|
|
20
|
+
Five more — 1.2.1 through 1.2.5, captions, transcripts, audio description —
|
|
21
|
+
arrive the moment the product ships video or audio, and they are a content
|
|
22
|
+
budget, not a code task.
|
|
23
|
+
|
|
24
|
+
This grouping is a judgement made in this file, not a cited study. It is
|
|
25
|
+
checkable: read each criterion and ask whether a correct implementation of a
|
|
26
|
+
wrong decision would still fail it.
|
|
27
|
+
|
|
28
|
+
W3C's framing, from an archived WAI resource that has not been superseded:
|
|
29
|
+
building accessibility in from the beginning is "almost always significantly
|
|
30
|
+
easier, less expensive, and more effective than making accessibility
|
|
31
|
+
improvements to an existing site later as a separate project"
|
|
32
|
+
([Financial Factors](https://www.w3.org/WAI/business-case/archive/fin), W3C WAI,
|
|
33
|
+
archived).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. The conformance target — versions and sources
|
|
38
|
+
|
|
39
|
+
**Default to WCAG 2.2 Level AA.** WCAG 2.2 is backward compatible, so hitting
|
|
40
|
+
2.2 AA satisfies every baseline below simultaneously. Targeting anything lower is
|
|
41
|
+
a choice to be non-conformant somewhere. Targeting AAA whole-site is a target
|
|
42
|
+
W3C itself says is not realistic — treat individual AAA criteria as opt-in
|
|
43
|
+
advisories.
|
|
44
|
+
|
|
45
|
+
| Jurisdiction / instrument | Version + level | Status |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| New Zealand Web Accessibility Standard 1.2 | **WCAG 2.2 AA** | In force 17 Mar 2025 — the strictest baseline |
|
|
48
|
+
| UK public sector | WCAG 2.2 AA | In force |
|
|
49
|
+
| EU — EN 301 549 v3.2.1 (EAA / WAD) | WCAG 2.1 AA | In force; moves to 2.2 AA with v4.1.1, expected in the OJEU ~Oct 2026 |
|
|
50
|
+
| US — ADA Title II (DOJ 2024 rule) | WCAG 2.1 AA | Deadlines 26 Apr 2027 (population ≥50,000) / 26 Apr 2028 |
|
|
51
|
+
| US — HHS Section 504 rule | WCAG 2.1 AA | First deadline 11 May 2026; **not** extended alongside DOJ's |
|
|
52
|
+
| US — Section 508 | WCAG 2.0 AA | Unchanged since the 2017/2018 ICT refresh |
|
|
53
|
+
| ISO/IEC 40500:2025 | WCAG 2.2 (Oct 2023 text) | Ratified 21 Oct 2025 |
|
|
54
|
+
|
|
55
|
+
**Two questions decide it. Ask once, record the answer.**
|
|
56
|
+
|
|
57
|
+
1. **Who is legally on the hook?** Public sector anywhere → that jurisdiction's
|
|
58
|
+
number. Selling into the EU → the European Accessibility Act has applied since
|
|
59
|
+
28 June 2025 and is extraterritorial: e-commerce, banking, e-books, transport
|
|
60
|
+
and telephony are in scope wherever the company sits. Microenterprises
|
|
61
|
+
(<10 staff, <€2m turnover) are exempt **as service providers only** — not as
|
|
62
|
+
manufacturers of covered products.
|
|
63
|
+
2. **What does the audience need beyond the floor?** Adopt individual AAA criteria
|
|
64
|
+
where they earn it — 2.4.13 Focus Appearance for a keyboard-heavy internal
|
|
65
|
+
tool, 1.4.6 Contrast Enhanced (7:1) for use in sunlight or by an older cohort.
|
|
66
|
+
Name them on their own line. Do not raise the whole level.
|
|
67
|
+
|
|
68
|
+
**Do not write** "the target is to be accessible", "fully compliant", or a
|
|
69
|
+
percentage. §3's honesty rules apply to plans as much as to reports: a target is
|
|
70
|
+
a target, not an outcome, and a plan that promises conformance is making the
|
|
71
|
+
claim shape that drew a $1,000,000 FTC penalty against an overlay vendor in 2025.
|
|
72
|
+
|
|
73
|
+
**WCAG 3 is not a target.** It is a Working Draft (3 March 2026) that forbids
|
|
74
|
+
citation as anything but work in progress and states its requirement set will
|
|
75
|
+
change. Practitioner estimates put a Recommendation at 2030 or later. A roadmap
|
|
76
|
+
line saying "prepare for WCAG 3" commits budget to a moving draft. Skip it.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. Decisions that foreclose accessibility
|
|
81
|
+
|
|
82
|
+
§0 names these; here is what each one costs and what to offer instead. Raise the
|
|
83
|
+
row **in the plan**, with the criterion and the alternative in the same breath.
|
|
84
|
+
The decision belongs to the user; the disclosure belongs to you.
|
|
85
|
+
|
|
86
|
+
| Product decision | Criterion at risk | Why code cannot save it | Alternative, decided now |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| Drag-and-drop as the only way to reorder or move | 2.5.7 Dragging Movements (AA, new in 2.2) | The whole interaction *is* the gesture; the alternative is a second UI, not an attribute | Move up / move down buttons, or a "Move to…" menu, in the component spec |
|
|
89
|
+
| Infinite scroll as the only route to content | 2.4.1 Bypass Blocks (A), 2.1.1 Keyboard (A) | Content that exists only after N scroll events is unreachable for anyone who needs the footer | Pagination, a "load more" **button**, or a search/index route to the same items |
|
|
90
|
+
| Hover-only navigation or hover-revealed actions | 1.4.13 Content on Hover or Focus (AA), 2.1.1 (A) | A hover menu retrofitted with focus handlers usually still fails dismissible / hoverable / persistent | Click-to-open disclosure or menu button, with the APG contract from the start |
|
|
91
|
+
| Charts drawn to `<canvas>`/WebGL with no data behind them | 1.1.1 Non-text Content (A), 1.4.11 (AA) | Canvas has no accessibility tree; `alt` on a canvas does not substitute for the data | Ship the underlying table (visually hidden or in a disclosure), or SVG with real text |
|
|
92
|
+
| A CAPTCHA as the only gate | 3.3.8 Accessible Authentication (AA, new in 2.2), 1.1.1 (A) | Object-recognition CAPTCHAs are permitted only as *an* alternative, never as the sole path | A vendor with a non-cognitive path; never block paste or password managers |
|
|
93
|
+
| Hard session timeout on a form or checkout | 2.2.1 Timing Adjustable (A), 3.3.7 Redundant Entry (A) | A timeout is a backend contract; extending it later means touching auth | Warn and extend, or persist partial input, specified in the flow |
|
|
94
|
+
| Autoplaying carousel or moving hero | 2.2.2 Pause, Stop, Hide (A) | A pause control is a component redesign, not a CSS rule | Static hero, or a carousel with a real pause control in the spec |
|
|
95
|
+
| Audio that plays automatically for over 3s | 1.4.2 Audio Control (A) | Retrofitting a control means restructuring the player | Do not autoplay; or ship pause/stop and independent volume |
|
|
96
|
+
| Text baked into images (hero art, pricing graphics) | 1.4.5 Images of Text (AA), 1.4.4 Resize Text (AA) | Fixing means re-cutting every asset | Real text over the image; reserve images-of-text for logos |
|
|
97
|
+
| Fixed-width or absolutely-positioned layout | 1.4.10 Reflow (AA), 1.4.12 Text Spacing (AA) | Reflow at 320px is a layout system, retrofitted only by rewriting it | Flow layout with relative units, agreed in the design system |
|
|
98
|
+
| Dense icon toolbars or table-row action links | 2.5.8 Target Size (AA, new in 2.2) | 24×24 CSS px is a spacing decision the whole visual design rests on | Set the minimum hit area as a design token before the first component |
|
|
99
|
+
| A custom widget where a native element or APG pattern would serve | 4.1.2 (A), 2.1.1 (A), plus the pattern's own contract | "A role is a promise" — the contract is the work, and it is always underestimated | The native element, or the named APG pattern with its keyboard table |
|
|
100
|
+
| No dark palette, "we'll add it later" | 1.4.3 (AA), 1.4.11 (AA) | Dark-mode contrast failures are invisible to a light-only review and touch every token | Define both ramps now (§4); the audit's dark pass then verifies them for free |
|
|
101
|
+
|
|
102
|
+
**None of these is forbidden.** Infinite scroll with a working search route, or
|
|
103
|
+
drag-and-drop with a real keyboard alternative, can be fine. What is not fine is
|
|
104
|
+
discovering the requirement after the component ships.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 3. Per-component criteria: resolving the words first
|
|
109
|
+
|
|
110
|
+
§0 asks each component to carry its success criteria and keyboard contract. That
|
|
111
|
+
is only possible once the component's *name* is resolved, and plan language is
|
|
112
|
+
where the wrong pattern gets chosen — the words are ambiguous, the contracts are
|
|
113
|
+
not. Ask in this order and record the answer:
|
|
114
|
+
|
|
115
|
+
1. **Is there a native element?** `<button>` `<a href>` `<select>` `<details>`
|
|
116
|
+
`<dialog>` `<input type=radio|checkbox|range|date>` `<table>`. If yes, that is
|
|
117
|
+
the answer — the browser supplies focus, keyboard, and state announcement, and
|
|
118
|
+
they cannot drift out of sync with the visuals.
|
|
119
|
+
2. **Is there an APG pattern?** One of the 30 in
|
|
120
|
+
[apg-patterns.md](apg-patterns.md). Name it in the plan and take its keyboard
|
|
121
|
+
contract as a requirement, not a nice-to-have.
|
|
122
|
+
3. **Custom.** Last resort. The plan must say which role's contract it
|
|
123
|
+
approximates, who writes the keyboard handling, and that it needs a manual
|
|
124
|
+
keyboard and screen reader pass — no automated rule covers a pattern nobody
|
|
125
|
+
has specified.
|
|
126
|
+
|
|
127
|
+
| Word in the plan | What it might mean | Cheapest correct reading |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| "dropdown" | `<select>`, menu button, combobox, disclosure, listbox — four different keyboard contracts | `<select>` unless custom option rendering is required |
|
|
130
|
+
| "modal" / "popup" | dialog, alertdialog, non-modal popover, tooltip | native `<dialog>` + `showModal()` |
|
|
131
|
+
| "toggle" | switch, checkbox, toggle button (`aria-pressed`) | switch for immediate effect; checkbox if a form submits it |
|
|
132
|
+
| "menu" | `role="menu"` (actions) or navigation (links) | a `<ul>` of links in a disclosure — `role="menu"` is for actions only |
|
|
133
|
+
| "stepper" | wizard, spinbutton, number input | `<input type="number">`, or a plain multi-step form |
|
|
134
|
+
| "date picker" | custom grid widget | `<input type="date">` first; it is the largest contract you can avoid |
|
|
135
|
+
| "tooltip" | a tooltip, or an accessible name that is missing | name the control with `aria-label`; a tooltip is never a name |
|
|
136
|
+
| "filter panel" | disclosure + `<fieldset>` groups, or a custom listbox | disclosure + native inputs, with a live region for result counts |
|
|
137
|
+
|
|
138
|
+
Write the resolved reading, not the word: `Sort control → native <select>`, not
|
|
139
|
+
`Sort control → dropdown`.
|
|
140
|
+
|
|
141
|
+
**Criteria blocks to paste into the plan line for each component.** An acceptance
|
|
142
|
+
criterion is falsifiable and names its criterion; "must be accessible" is neither,
|
|
143
|
+
which is why the ticket closes with nothing checked.
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Any component: 2.1.1 keyboard · 2.4.7 focus visible · 1.4.3 + 1.4.11 contrast
|
|
147
|
+
(light and dark) · 2.5.8 target size · 1.4.10 reflow at 320px
|
|
148
|
+
Form: 1.3.1 + 3.3.2 labels · 1.3.5 autocomplete · 3.3.1 error
|
|
149
|
+
identification · 3.3.3 error suggestion · 3.3.7 redundant
|
|
150
|
+
entry · 3.3.8 paste and password managers work
|
|
151
|
+
Dialog / menu / the full APG keyboard contract, keys enumerated · focus in on
|
|
152
|
+
tabs / combobox open, contained, returned to the trigger on close · Escape
|
|
153
|
+
Data / chart / 1.3.1 <th> + scope · 1.1.1 data available as text · 1.4.1 no
|
|
154
|
+
table meaning by color alone
|
|
155
|
+
Media: 1.2.2 captions · 1.2.1 transcript · 1.2.5 audio description ·
|
|
156
|
+
2.2.2 / 1.4.2 nothing autoplays without a control
|
|
157
|
+
Images and icons: 1.1.1 — alt text and accessible names confirmed by a human who
|
|
158
|
+
has seen the image
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
That last line is a **person, not a check**. Generated alt text ships as DRAFT
|
|
162
|
+
(§3) and is not a satisfied criterion until someone who can see the image says so.
|
|
163
|
+
It is the one item an agent cannot close on its own; the plan should name who does.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 4. Color tokens, before the components exist
|
|
168
|
+
|
|
169
|
+
Low-contrast text is the most common failure in the wild — **83.9% of home
|
|
170
|
+
pages**, averaging **34 distinct instances** on each affected page. It is also the
|
|
171
|
+
only top-six failure fully decided by a list of hex values someone chooses once.
|
|
172
|
+
|
|
173
|
+
**Enumerate the pairs, not the colors.** Contrast is a property of a pair. A
|
|
174
|
+
palette of 12 tokens is not 12 decisions; it is however many
|
|
175
|
+
foreground-on-background combinations the design actually uses: body on surface,
|
|
176
|
+
body on raised surface, muted on surface, link on surface, button label on
|
|
177
|
+
primary, button label on primary-hover, border on surface, focus ring on surface,
|
|
178
|
+
focus ring on primary, error on surface, badge text on each badge fill. Then the
|
|
179
|
+
whole list again for dark.
|
|
180
|
+
|
|
181
|
+
**Resolve each pair with the CLI, not by eye.**
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
A11Y contrast "#7C9EC4" "#ffffff" --fix # body text: needs 4.5:1
|
|
185
|
+
A11Y contrast "#7C9EC4" "#ffffff" --large --fix # ≥24px, or ≥18.5px bold: 3:1
|
|
186
|
+
A11Y contrast "#7C9EC4" "#ffffff" --ui --fix # borders, focus rings, icons: 3:1
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`--fix` returns a lighter and a darker candidate reaching the threshold with hue
|
|
190
|
+
and chroma preserved in OKLCh, plus before/after ratios. Take a candidate rather
|
|
191
|
+
than guessing. Exit code is `0` if the pair passes AA and `1` if not, so a
|
|
192
|
+
plan-time sweep is scriptable. If `--fix` returns no candidate, that is not "try
|
|
193
|
+
harder" — lightness alone cannot fix that pair, and the hue or the pairing itself
|
|
194
|
+
has to change. Record it; that is a design decision, not an implementation detail.
|
|
195
|
+
|
|
196
|
+
**Optionally, prove the whole palette in one audit — still before any component.**
|
|
197
|
+
Write a throwaway proof sheet: one row per pair, real text at its real size on its
|
|
198
|
+
real background, plus a `prefers-color-scheme: dark` block with the dark ramp.
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
A11Y audit --file .a11y/palette-proof.html --json --out .a11y/palette.json
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The default pass checks light, the **dark pass checks the dark ramp**, and the
|
|
205
|
+
**forced-colors pass** surfaces every token hard-coded where a system color
|
|
206
|
+
belongs — the most common reason a carefully built palette disappears in Windows
|
|
207
|
+
High Contrast. This is the earliest point in a project where `audit` returns a
|
|
208
|
+
real answer, and it costs one HTML file.
|
|
209
|
+
|
|
210
|
+
**Write ratios into the plan, not just hex.** A ratio is checkable by anyone
|
|
211
|
+
later; a hex alone is not.
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
- Color tokens: --text-body #1F2937 on --surface #FFFFFF — 15.29:1 light / 14.61:1 dark
|
|
215
|
+
--link #2563EB on #FFFFFF — 5.17:1 light; dark #93C5FD — 7.42:1
|
|
216
|
+
--border #D1D5DB on #FFFFFF — 1.47:1 FAIL (ui) → #6B7280, 4.83:1
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Two things a ratio does not settle.** Contrast over gradients, images, video and
|
|
220
|
+
translucent overlays is where the engine returns `needsReview` — decide at plan
|
|
221
|
+
time whether the hero gets a solid scrim, because "text over a photo" produces an
|
|
222
|
+
unresolvable finding every time. And never encode meaning in color alone (SC
|
|
223
|
+
1.4.1): if the plan says "red for over budget", it must say what the non-color cue
|
|
224
|
+
is.
|
|
225
|
+
|
|
226
|
+
**APCA does not belong in the plan.** Its critique of WCAG 2 is legitimate, but
|
|
227
|
+
the WCAG 3 contrast algorithm is explicitly "yet to be determined" as of the April
|
|
228
|
+
2026 editor's draft, and Lc values have no normative or legal standing. Never let
|
|
229
|
+
an APCA pass excuse a WCAG 2 failure in a design review.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 5. Vetting a component library before adopting it
|
|
234
|
+
|
|
235
|
+
"Accessible components" on a landing page is a marketing claim, not a test result
|
|
236
|
+
— and it is frequently true *of the library* and false *of your build*. Every
|
|
237
|
+
headless library ships unstyled, so focus visibility (2.4.7), contrast (1.4.3,
|
|
238
|
+
1.4.11) and target size (2.5.8) are yours regardless. A library moves the
|
|
239
|
+
ARIA-and-keyboard half of the problem, not the visual half.
|
|
240
|
+
|
|
241
|
+
The stake is real: pages using ARIA average **59.1 errors versus 42.0 on pages
|
|
242
|
+
without it** (WebAIM Million 2026). A library that gets ARIA right is worth a
|
|
243
|
+
great deal; one that gets it confidently wrong is worse than no library.
|
|
244
|
+
|
|
245
|
+
**Seven checks, under an hour.**
|
|
246
|
+
|
|
247
|
+
1. **An accessibility conformance report or VPAT — dated, naming the version?**
|
|
248
|
+
Absence is not disqualifying. Presence tells you someone was accountable for a
|
|
249
|
+
specific release.
|
|
250
|
+
2. **Do the docs publish a keyboard contract per component?** Compare against
|
|
251
|
+
[apg-patterns.md](apg-patterns.md). A library documenting arrow keys, Home/End
|
|
252
|
+
and Escape per component has read the APG. One whose docs show only props has
|
|
253
|
+
not.
|
|
254
|
+
3. **Search the issue tracker for `label:accessibility` — open *and* closed.** The
|
|
255
|
+
signal is not the count, it is the response: triaged and fixed, or open for two
|
|
256
|
+
years? Filed by maintainers, or only by users?
|
|
257
|
+
4. **Does it ship a focus-visible style, or leave it to you?** Headless libraries
|
|
258
|
+
(Radix, Headless UI, and the shadcn/ui components built on Radix) deliberately
|
|
259
|
+
leave it to you. That is a correct design choice and a task on your plan.
|
|
260
|
+
5. **Does it render real semantics?** Inspect one shipped example: is the button a
|
|
261
|
+
`<button>`, is the dialog a `<dialog>` or at least `aria-modal` with the rest
|
|
262
|
+
inert, is the tab list a real `role="tablist"` with roving tabindex?
|
|
263
|
+
6. **What happens in dark mode and forced-colors?** Most libraries have never been
|
|
264
|
+
tested under `forced-colors: active`. Render the library's own example page and
|
|
265
|
+
audit it — the forced-colors and dark passes tell you more than the README.
|
|
266
|
+
7. **What is yours regardless?** Enumerate it: contrast, focus indicator, target
|
|
267
|
+
size, heading outline, landmark structure, alt text, live regions for your
|
|
268
|
+
async states, error copy. No library supplies any of these.
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
- Library: <name> <version> — APG contracts documented: <yes/partial/no>
|
|
272
|
+
Provided: <roles / keyboard / focus trap / …>
|
|
273
|
+
Ours regardless: focus-visible styling, contrast tokens, target size, headings, alt text
|
|
274
|
+
Verified: audited <lib>'s own example under the five passes on <date>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Do not write, and do not repeat to the user,** that a library "is accessible",
|
|
278
|
+
"is WCAG compliant", or that using it makes the app conformant. What a good
|
|
279
|
+
library gives you is a better starting point and a smaller §2 loop — worth saying
|
|
280
|
+
plainly, and a different claim.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 6. Structure decided once
|
|
285
|
+
|
|
286
|
+
Four artifacts that are cheap in a plan and expensive to retrofit, and that §1
|
|
287
|
+
then merely implements.
|
|
288
|
+
|
|
289
|
+
**Heading outline.** One `<h1>` per page or route; levels descend from the
|
|
290
|
+
information hierarchy, never from font size. Write the outline as an indented list
|
|
291
|
+
— that list *is* the SC 1.3.1 / 2.4.6 answer, and it settles the argument before
|
|
292
|
+
someone makes a plan name an `<h4>` because it looks right.
|
|
293
|
+
|
|
294
|
+
**Landmark map.** `header` / `nav` / `main` (exactly one) / `footer`, plus which
|
|
295
|
+
named regions exist and what names they carry. Decides SC 2.4.1, and it is the
|
|
296
|
+
difference between reaching content in one key press and tabbing through the nav
|
|
297
|
+
on every page.
|
|
298
|
+
|
|
299
|
+
**Focus order and keyboard map.** The intended tab order per screen, and any
|
|
300
|
+
element needing `tabindex="-1"` as a programmatic target. A tool can only report
|
|
301
|
+
that focus order diverges from DOM order — only a human can say which order is
|
|
302
|
+
*right*, so the plan is where "right" gets defined. Positive `tabindex` is never
|
|
303
|
+
the mechanism.
|
|
304
|
+
|
|
305
|
+
**SPA route-change focus.** Name the target (the new `<h1>`, or a `tabindex="-1"`
|
|
306
|
+
container) and who updates `document.title`. One line in a plan; a cross-cutting
|
|
307
|
+
refactor once the router is built and focus already falls to `<body>` on every
|
|
308
|
+
navigation.
|
|
309
|
+
|
|
310
|
+
**Skip link** to `<main>` on every full page. One line, and SC 2.4.1 is closed.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 7. Planning the verification and the manual budget
|
|
315
|
+
|
|
316
|
+
**The `--interact` inventory.** Every state that does not exist at page load is
|
|
317
|
+
invisible to a load-time scan. List them at plan time — each modal, error state,
|
|
318
|
+
expanded menu, async result list, route. That list becomes `.a11y/states.mjs` and
|
|
319
|
+
the ticket's audit command. Written later, it is written from memory and comes out
|
|
320
|
+
short.
|
|
321
|
+
|
|
322
|
+
**Where the gates sit.** At minimum: per component before the ticket closes, and
|
|
323
|
+
`diff --before --after` on the branch before merge. `diff` exits `1` on a NEW
|
|
324
|
+
finding even when the total count fell, which is what makes it a gate rather than
|
|
325
|
+
a report.
|
|
326
|
+
|
|
327
|
+
**The manual budget, as a line item with a name against it.** A keyboard pass (the
|
|
328
|
+
five-minute script in [manual-testing.md](manual-testing.md)), a screen reader
|
|
329
|
+
pass (NVDA is free on Windows; VoiceOver is built into macOS and iOS), alt-text
|
|
330
|
+
confirmation, and — the one that changes outcomes rather than scores — a session
|
|
331
|
+
with a disabled user before launch rather than after.
|
|
332
|
+
|
|
333
|
+
**State the reach up front.** Of the 55 A/AA criteria, 17 have any ACT-approved
|
|
334
|
+
automated rule at all and 7 are flagged reliably; 9 cannot be meaningfully tested
|
|
335
|
+
by any tool. A plan that budgets only for the audit has budgeted for a minority of
|
|
336
|
+
the work. The honest denominators belong in the plan for the same reason they
|
|
337
|
+
belong in the report (§3) — and the plan is the only place where naming them can
|
|
338
|
+
still change what gets funded.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 8. What this costs
|
|
343
|
+
|
|
344
|
+
Built in from the start, accessibility is "often a small percentage of the overall
|
|
345
|
+
website cost"; retrofitted, it is a separate project
|
|
346
|
+
([W3C WAI](https://www.w3.org/WAI/business-case/archive/fin), archived).
|
|
347
|
+
|
|
348
|
+
The mechanism persuades better than any percentage: the same six failure classes
|
|
349
|
+
have accounted for **96% of all detected errors for seven consecutive years**
|
|
350
|
+
while the per-page average *rose* to 56.1. Detection was never the bottleneck.
|
|
351
|
+
What changes the number is the decision, and the decision is made here.
|
|
352
|
+
|
|
353
|
+
**No cost multiplier appears in this file.** The "100× to fix after release"
|
|
354
|
+
figure has no primary source, and "67% of issues originate in design" is not
|
|
355
|
+
stated on the pages it is attributed to. A plan that justifies itself with a
|
|
356
|
+
fabricated number is easier to dismiss than one that does not.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Related
|
|
361
|
+
|
|
362
|
+
- [wcag22-quick-ref.md](wcag22-quick-ref.md) — the 55 criteria the plan is choosing among
|
|
363
|
+
- [apg-patterns.md](apg-patterns.md) — the keyboard contracts a pattern choice commits to
|
|
364
|
+
- [ai-failure-modes.md](ai-failure-modes.md) — what the code will do wrong once building starts
|
|
365
|
+
- [manual-testing.md](manual-testing.md) — what to put in the manual budget
|
|
@@ -219,6 +219,7 @@ normative or legal standing. Never let an APCA pass excuse a WCAG 2 failure.
|
|
|
219
219
|
|
|
220
220
|
## Related
|
|
221
221
|
|
|
222
|
+
- Choosing which of these a project targets, before the code: [plan-phase.md](plan-phase.md)
|
|
222
223
|
- ARIA and widget behavior: [apg-patterns.md](apg-patterns.md)
|
|
223
224
|
- What generated code gets wrong: [ai-failure-modes.md](ai-failure-modes.md)
|
|
224
225
|
- What none of this can check: [manual-testing.md](manual-testing.md)
|