Accessibility verification
A clean automated scan proves that a set of machine-checkable rules found nothing. It says nothing
about whether a name is meaningful, an order is logical, an announcement is useful, or a flow can be
completed without a mouse. Those need a person driving the interface.
When this fires
Before any claim that a screen, component or flow is accessible, keyboard-operable, screen-reader
usable or WCAG-conformant — your own work or someone else's, and when re-checking after fixes. It
does not fire to write the fixes; that is accessibility.
Procedure
- Fix the scope in writing first. Which routes, which states (empty, loading, error, logged in),
which viewport, which browser. A claim without a named scope is not a claim — everything outside
it stays explicitly unverified.
- Render the real thing. The running app at the route that changed, not the source, not
Storybook alone if the route composes differently. A stale build verifies nothing.
- Run an automated scan and record it exactly. Use whatever the project already has — an
axe-based checker, the browser's accessibility audit, a CI accessibility step. Record the tool and
version, the URL, the rule ids that failed and the count. Treat the result as a floor: scanners
find only a minority of real barriers, and they cannot judge meaning or order at all.
- Do the keyboard pass with the pointer unused. Tab from the top: write down the order, and
whether it matches the visual order. Check the skip link, every control being reachable and
operable with Enter/Space/arrows, Escape closing overlays, no trap, focus returning after a dialog
closes, and the focus indicator being visible and not covered by sticky headers or banners.
- Do the screen-reader pass on at least one real pairing — VoiceOver with Safari, or NVDA with
Firefox. Read the flow start to finish, then complete the task with the screen buffer as your only
information. Check: does every control announce a name, a role and its state; do headings and
landmarks describe the page; do errors and status changes get announced when they happen. See
references/screen-reader-passes.md for how to drive each one.
- Check zoom, reflow and text spacing. 200% browser zoom, and a 320 CSS px wide viewport: no
horizontal scrolling, no content lost, no overlap. Then apply the text-spacing condition
(line height 1.5×, paragraph spacing 2×, letter spacing 0.12em, word spacing 0.16em) by injecting
CSS, and confirm nothing is clipped or overlapped.
- Measure contrast on the rendered pixels, in every state — default, hover, focus, selected,
error, and text over images or gradients. A token that passes in the palette can fail in place.
- Reach the non-happy states deliberately — submit the form empty, trigger the error, load the
empty list — and verify each announces itself rather than only changing colour.
- Re-run the whole pass after fixes. A fix is verified by repeating step 3 onward, never by the
reasoning that produced it. Regressions from accessibility fixes are common, especially in focus
handling.
- Stop and ask before anything outward-facing. Publishing an accessibility statement, filling a
VPAT or answering a customer conformance questionnaire is a formal representation about the
product. This procedure supplies evidence for that; it does not authorize making it.
Checklist
Failure handling
- No screen reader available — report that the screen-reader checks were not performed, list what
was. The claim then stops at "keyboard-operable and scan-clean on ". Do not substitute the
accessibility tree for a screen-reader pass: the tree shows what is exposed, not what is announced,
in what order, or whether it makes sense.
- The scan reports violations in third-party code — record them separately with their source. They
are still barriers for users; they are just not fixable in this diff.
- A barrier will not reproduce — say so with what you tried. Screen-reader behaviour varies by
reader, browser and version; name the exact combination.
- Scan is clean but the keyboard pass fails — the keyboard result wins. Report the scan as
uninformative here, not as a pass.
- Time ran out mid-pass — report what was covered and what was not. A partial pass honestly
scoped is useful; a partial pass reported as complete is worse than none.
What this refuses to conclude
- Zero automated violations is not "accessible" and not "WCAG 2.2 AA".
- A passing keyboard pass is not a passing screen-reader pass.
- One route verified is not the product verified.
- Code that was written following accessible patterns is not verified code — it is unverified
code that will probably pass.
- Conformance is a claim about a defined scope against defined criteria. Without the scope and the
per-criterion evidence, this procedure produces findings, not conformance.
Evidence to report
Scope; the scan tool, version and output; the tab order as observed, with any traps or invisible
stops; the reader + browser pairing and what it actually announced at each step, quoted; the zoom,
reflow and text-spacing results; contrast ratios measured, with the states they were measured in;
screenshots where a picture carries what words cannot. Then the list of criteria, routes and states
not checked. "Accessibility verified" without those is an assertion, not evidence.
1---2name: accessibility-verification3description: Prove an accessibility claim instead of asserting it — automated scan plus the keyboard, focus, zoom and screen-reader passes a scanner structurally cannot make. Fires before anyone says a screen is accessible or WCAG AA, when signing off UI work, or when an audit result needs checking. Not for making the fixes (accessibility), and it never upgrades a clean scan into a conformance claim.4---56# Accessibility verification78A clean automated scan proves that a set of machine-checkable rules found nothing. It says nothing9about whether a name is meaningful, an order is logical, an announcement is useful, or a flow can be10completed without a mouse. Those need a person driving the interface.1112## When this fires1314Before any claim that a screen, component or flow is accessible, keyboard-operable, screen-reader15usable or WCAG-conformant — your own work or someone else's, and when re-checking after fixes. It16does not fire to write the fixes; that is `accessibility`.1718## Procedure19201. **Fix the scope in writing first.** Which routes, which states (empty, loading, error, logged in),21 which viewport, which browser. A claim without a named scope is not a claim — everything outside22 it stays explicitly unverified.232. **Render the real thing.** The running app at the route that changed, not the source, not24 Storybook alone if the route composes differently. A stale build verifies nothing.253. **Run an automated scan and record it exactly.** Use whatever the project already has — an26 axe-based checker, the browser's accessibility audit, a CI accessibility step. Record the tool and27 version, the URL, the rule ids that failed and the count. Treat the result as a floor: scanners28 find only a minority of real barriers, and they cannot judge meaning or order at all.294. **Do the keyboard pass with the pointer unused.** Tab from the top: write down the order, and30 whether it matches the visual order. Check the skip link, every control being reachable and31 operable with Enter/Space/arrows, Escape closing overlays, no trap, focus returning after a dialog32 closes, and the focus indicator being visible *and not covered* by sticky headers or banners.335. **Do the screen-reader pass on at least one real pairing** — VoiceOver with Safari, or NVDA with34 Firefox. Read the flow start to finish, then complete the task with the screen buffer as your only35 information. Check: does every control announce a name, a role and its state; do headings and36 landmarks describe the page; do errors and status changes get announced when they happen. See37 `references/screen-reader-passes.md` for how to drive each one.386. **Check zoom, reflow and text spacing.** 200% browser zoom, and a 320 CSS px wide viewport: no39 horizontal scrolling, no content lost, no overlap. Then apply the text-spacing condition40 (line height 1.5×, paragraph spacing 2×, letter spacing 0.12em, word spacing 0.16em) by injecting41 CSS, and confirm nothing is clipped or overlapped.427. **Measure contrast on the rendered pixels, in every state** — default, hover, focus, selected,43 error, and text over images or gradients. A token that passes in the palette can fail in place.448. **Reach the non-happy states deliberately** — submit the form empty, trigger the error, load the45 empty list — and verify each announces itself rather than only changing colour.469. **Re-run the whole pass after fixes.** A fix is verified by repeating step 3 onward, never by the47 reasoning that produced it. Regressions from accessibility fixes are common, especially in focus48 handling.4910. **Stop and ask before anything outward-facing.** Publishing an accessibility statement, filling a50 VPAT or answering a customer conformance questionnaire is a formal representation about the51 product. This procedure supplies evidence for that; it does not authorize making it.5253## Checklist5455- [ ] Scope written down: routes, states, viewport, browser56- [ ] The running app was exercised, not the source read57- [ ] Automated scan run; tool, version and exact failures recorded58- [ ] Full keyboard pass done with no pointer use; tab order written down59- [ ] Focus visible at every stop and not obscured by overlaying chrome60- [ ] Screen-reader pass on a named reader + browser pairing, completing the real task61- [ ] Names, roles and states confirmed as announced — not assumed from the markup62- [ ] 200% zoom and 320px reflow checked; text-spacing condition applied63- [ ] Contrast measured on rendered pixels in every interactive state64- [ ] Error and status messages heard, not just seen65- [ ] Everything out of scope or unchecked is named in the report6667## Failure handling6869- **No screen reader available** — report that the screen-reader checks were not performed, list what70 was. The claim then stops at "keyboard-operable and scan-clean on <scope>". Do not substitute the71 accessibility tree for a screen-reader pass: the tree shows what is exposed, not what is announced,72 in what order, or whether it makes sense.73- **The scan reports violations in third-party code** — record them separately with their source. They74 are still barriers for users; they are just not fixable in this diff.75- **A barrier will not reproduce** — say so with what you tried. Screen-reader behaviour varies by76 reader, browser and version; name the exact combination.77- **Scan is clean but the keyboard pass fails** — the keyboard result wins. Report the scan as78 uninformative here, not as a pass.79- **Time ran out mid-pass** — report what was covered and what was not. A partial pass honestly80 scoped is useful; a partial pass reported as complete is worse than none.8182## What this refuses to conclude8384- Zero automated violations is **not** "accessible" and **not** "WCAG 2.2 AA".85- A passing keyboard pass is **not** a passing screen-reader pass.86- One route verified is **not** the product verified.87- Code that was written following accessible patterns is **not** verified code — it is unverified88 code that will probably pass.89- Conformance is a claim about a defined scope against defined criteria. Without the scope and the90 per-criterion evidence, this procedure produces findings, not conformance.9192## Evidence to report9394Scope; the scan tool, version and output; the tab order as observed, with any traps or invisible95stops; the reader + browser pairing and what it actually announced at each step, quoted; the zoom,96reflow and text-spacing results; contrast ratios measured, with the states they were measured in;97screenshots where a picture carries what words cannot. Then the list of criteria, routes and states98**not** checked. "Accessibility verified" without those is an assertion, not evidence.