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>
3.3 KiB
3.3 KiB
WP-16 — Component a11y: description wiring + alert role
Status: todo Phase: 4 — a11y
Why
Audit findings axe can't (fully) catch:
form-fieldrenders a description<div [id]="fieldId()+'-desc'">that no control ever references —text-inputsetsaria-describedbyonly to…-errorand only when invalid. Screen readers never announce field descriptions (e.g. the BSN hint on the login form).- The field↔control id pairing is manual (
fieldIdmust equal the input'sname/id) with nothing enforcing it. - The
alertatom is alwaysrole="status"— error alerts are announced politely while other errors in the app userole="alert"; urgency is inconsistent.
Read first
src/app/shared/ui/form-field/form-field.component.ts(~line 15)src/app/shared/ui/text-input/text-input.component.ts(~lines 17-18)src/app/shared/ui/radio-group/radio-group.component.ts,checkbox/checkbox.component.tssrc/app/shared/ui/alert/alert.component.tssrc/app/auth/ui/login-form/login-form.component.ts(a live desc that's never wired)
Decisions (pre-made, don't relitigate)
aria-describedby= space-joined ids:-descalways when a description exists +-errorwhen invalid; order pinned (desc first, error second).- Pairing contract: form-field exposes its
fieldId; the composition contract (fieldId === control id/name) is documented in both components and enforced by a story play test on the canonical form-field+text-input composition (expect(input).toHaveAttribute('aria-describedby', 'x-desc'), flip validity, assert'x-desc x-error'). Play tests run in the WP-01 test-runner for free. - DI-based auto-wiring (form-field providing the id via injection) is out of scope — more clever than this POC needs; the play test catches drift. Revisit only if the manual contract actually breaks in practice.
- Alert:
type === 'error'→role="alert"; others keeprole="status". Rationale comment in the atom.
Files
form-field.component.ts,text-input.component.ts,radio-group.component.ts,checkbox.component.ts(describedby joins; only where the component takes a hint)alert.component.ts(+ story asserting the role per variant)form-field.stories.ts(or a composition story) with the play test- Call sites that pass descriptions (verify login-form BSN hint is now announced)
Steps
- Implement the describedby join in the input atoms; form-field renders
-desconly when a description input is set (it already does — verify). - Write the play tests (form-field composition + alert roles).
- Alert role switch + rationale comment.
- Manual screen-reader spot check (optional but recommended — note result here).
Acceptance criteria
- Description text is programmatically associated in the canonical composition; login-form BSN hint announced.
-errorid appended exactly when invalid; order stable.- Error alerts are
role="alert"; play tests assert both behaviors and run in the CI gate.
Verification
GREEN + npm run test-storybook:ci (includes the new play tests).
Out of scope
Route-change focus and template lint (WP-17); rewriting form-field's layout.
Risks
aria-describedby churn on validity flips re-announcing content — pinned order + play
test coverage keeps it deterministic.