Files
atomic-design-poc/docs/backlog/WP-17-app-a11y.md
Edwin van den Houdt e82309786d style: format frontend, docs and skills with prettier; add .prettierignore
One-time prettier --write so the new format:check CI gate starts green.
.prettierignore excludes generated (api-client.ts, documentation.json),
vendored (public/cibg-huisstijl), and backend (dotnet format owns it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:39:31 +02:00

84 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WP-17 — App-level a11y: route focus/scroll, template lint, WCAG checklist + MDX
Status: todo
Phase: 4 — a11y
## Why
Three app-level gaps close the WCAG story:
- **No route-change focus/scroll management** — `app.config.ts` has only
`provideRouter(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-eslint` is entirely absent.
- User decision: a **manual WCAG checklist** documents what automation can't test.
## Read first
- `src/app/app.config.ts`
- `eslint.config.mjs`, `package.json`
- `src/app/shared/layout/shell/shell.component.ts` (`#main` target),
`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's `h1` (or `#main` fallback,
`tabindex="-1"`), **deferred via `afterNextRender`** so it coexists with view
transitions; skip the initial load. Implement as a small root service/effect wired in
`app.config.ts` — not per page.
- Lint: `angular-eslint` at the version matching Angular 22 (~v21+ — check
`npm view angular-eslint peerDependencies`), with `processInlineTemplates` (this repo
has **no** `.html` templates). 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 small `shared/` focus-on-navigation service
- `eslint.config.mjs`, `package.json` (angular-eslint)
- Any template the new lint rules flag
- New `docs/wcag-checklist.md`
- New `src/docs/a11y.mdx` — title `Foundations/Accessibility`
## Steps
1. Router scroll + focus service; manual keyboard smoke (tab through, navigate, confirm
focus lands on the new page's heading).
2. Add angular-eslint; enable the template rules on inline templates; plant a bad
pattern, see it fail, remove it; fix real hits.
3. `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).
4. `src/docs/a11y.mdx`: the layered approach — axe gate (WP-01) + template lint + play
tests (WP-16) + manual checklist; the alert-role rationale; where the
`a11y.disable` skip 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.mdx` renders; links to checklist and WP-01 skip rules.
## 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 `withViewTransitions` timing → `afterNextRender` + test in a real browser
(`npm start`), not just unit-level.