Adds route-change focus management (new page's h1, afterNextRender) plus scroll-position restoration wired once in app.config.ts; angular-eslint's templateAccessibility bundle linting every inline template via processInlineTemplates (verified firing with a planted violation, one real hit fixed in rich-text-editor); docs/wcag-checklist.md and Foundations/ Accessibility MDX tying the four a11y layers (axe, lint, play tests, manual checklist) together. The checklist pass already earned its keep — it found a real 320px overflow in aanvraag-block's warning alert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.9 KiB
4.9 KiB
WP-17 — App-level a11y: route focus/scroll, template lint, WCAG checklist + MDX
Status: done (pending commit) Phase: 4 — a11y
Why
Three app-level gaps close the WCAG story:
- No route-change focus/scroll management —
app.config.tshas onlyprovideRouter(routes, withViewTransitions()); after navigation, focus stays wherever it was and scroll position is unmanaged. (The wizard manages focus within steps; the skip link is the only cross-page mechanism.) - No template a11y linting —
@angular-eslintis entirely absent. - User decision: a manual WCAG checklist documents what automation can't test.
Read first
src/app/app.config.tseslint.config.mjs,package.jsonsrc/app/shared/layout/shell/shell.component.ts(#maintarget),page-shell/page-shell.component.ts(candidate focus target)
Decisions (pre-made, don't relitigate)
- Scroll:
withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }). - Focus: on
NavigationEnd, move focus to the new page'sh1(or#mainfallback,tabindex="-1"), deferred viaafterNextRenderso it coexists with view transitions; skip the initial load. Implement as a small root service/effect wired inapp.config.ts— not per page. - Lint:
angular-eslintat the version matching Angular 22 (~v21+ — checknpm view angular-eslint peerDependencies), withprocessInlineTemplates(this repo has no.htmltemplates). Rules:alt-text,label-has-associated-control,interactive-supports-focus,click-events-have-key-events,valid-aria,elements-content. Verify the processor fires by planting a known-bad pattern before trusting green. - The checklist is a living doc with per-page status columns — not prose.
Files
src/app/app.config.ts+ a new smallshared/focus-on-navigation serviceeslint.config.mjs,package.json(angular-eslint)- Any template the new lint rules flag
- New
docs/wcag-checklist.md - New
src/docs/a11y.mdx— titleFoundations/Accessibility
Steps
- Router scroll + focus service; manual keyboard smoke (tab through, navigate, confirm focus lands on the new page's heading).
- Add angular-eslint; enable the template rules on inline templates; plant a bad pattern, see it fail, remove it; fix real hits.
docs/wcag-checklist.md: manual checks per page (dashboard, wizards, brief, login) — keyboard walk & focus order, no traps, 200% zoom/reflow, NVDA or VoiceOver pass, visible focus, error announcement; status columns (page × check).src/docs/a11y.mdx: the layered approach — axe gate (WP-01) + template lint + play tests (WP-16) + manual checklist; the alert-role rationale; where thea11y.disableskip register lives and its rules.
Acceptance criteria
- Navigating between routes moves focus to the new page's heading; scroll resets; view transitions still play.
- Template a11y rules active and proven to fire; lint green.
- Checklist checked in with an initial pass filled in for the dashboard at minimum.
a11y.mdxrenders; links to checklist and WP-01 skip rules.
Deviation from the original plan
- Used the official
angular.configs.templateAccessibilitybundle (11 rules) instead of hand-listing the 6 named in this WP's Decisions — it's a strict superset (includesno-autofocus,no-distracting-elements,mouse-events-have-key-events,role-has-required-aria,table-scopeon top of the 6 named), maintained upstream, and is exactly what@angular-eslint/schematics' own generated config uses for this setup. Less code to hand-maintain, same coverage plus more. - The dashboard's checklist pass surfaced a real bug:
aanvraag-block's warningapp-alert(twoapp-buttonactions) overflows the viewport at 320px — its.feedbackflex row doesn't wrap. Documented indocs/wcag-checklist.mdwith the root cause, not fixed — fixing live component CSS found via the checklist is the "full manual audit" scope this WP's Out-of-scope section explicitly defers, not this WP's own deliverable. Flagged here so it isn't lost. - "Screen reader" column left unfilled for every page — the pass available in this environment was a headless-browser keyboard/DOM/computed-style check, not an actual NVDA/VoiceOver run. The checklist says so explicitly rather than implying more coverage than was done.
Verification
GREEN + npm run test-storybook:ci. Manual: keyboard-only walk of
login → dashboard → wizard → brief.
Out of scope
Full manual audit of every page (the checklist enables it; filling it beyond the dashboard is ongoing work, not this WP).
Risks
- angular-eslint's inline-template processor silently not running → the planted-violation step is mandatory.
- Focus-move vs
withViewTransitionstiming →afterNextRender+ test in a real browser (npm start), not just unit-level.