From 98a8c78a31a03b0d1dfbb661067727d613fe10c2 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Thu, 30 Apr 2026 13:36:00 +0200 Subject: [PATCH] docs: add ADR-0016 for accessibility baseline (WCAG 2.2 AA + targeted AAA, spartan-ng + CDK + Tailwind, APF panel) Pin the a11y baseline given the host organisation context (APF France Handicap, where accessibility is the core mission, not a compliance checkbox): - Conformance: WCAG 2.2 AA universal + AAA on criteria with high impact for the user base (1.4.6 Enhanced Contrast 7:1, 2.2.3 No Timing, 2.3.3 Animation from Interactions, 3.1.5 Reading Level, 1.4.8 Visual Presentation, 2.4.9 Link Purpose, 3.3.5 Help). RGAA 4.1 alignment for French audit context. - User-preferences panel as a first-class feature: contrast (3 tiers), text size (up to 200%), motion, text spacing, cognitive simplification, reading focus. Persisted in session (ADR-0010). - UI stack: Angular CDK + spartan-ng + TailwindCSS (not Angular Material; not React libs). Components copy-pasted into libs/shared/ui under our control. Design tokens with contrast-verified colour pairs in libs/shared/tokens. - Tooling and CI gates: @angular-eslint/template/* a11y rules (blocking), @axe-core/playwright e2e blocking on critical/serious violations, design-token contrast verifier (blocking), touch-target size check (blocking on 44x44 min, default 48x48). Wires into the a11y gate slot from ADR-0015. - Manual testing: keyboard and screen reader required on every UI PR via PR-template checklist; APF user-panel session before every major release covering visual / motor / cognitive / hearing categories. - Documentation: public accessibility statement at /accessibility (EN) and /accessibilite (FR) - EAA legal requirement, generated from source-of-truth in the repo. Internal patterns library in docs/accessibility/. decisions/README.md index updated. CLAUDE.md gains an explicit 'Accessibility' line pointing to ADR-0016 and the CI/CD line is adjusted to mark the a11y gate as locked-in (no longer 'future'). --- CLAUDE.md | 3 +- ...ssibility-baseline-wcag-aa-targeted-aaa.md | 244 ++++++++++++++++++ decisions/README.md | 1 + 3 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md diff --git a/CLAUDE.md b/CLAUDE.md index 02b059b..b1e1d2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,8 @@ The structural choices are recorded as ADRs and summarized below. Any change to - **Observability:** Pino + `nestjs-pino` for structured JSON logs, OpenTelemetry SDK + auto-instrumentations for traces, W3C Trace Context propagation across SPA → BFF → DB → Redis, `nestjs-cls` for request-scoped context (`trace_id`, `session_id`, `user_id_hash`, `audience`), 100 % sampling at the app with tail sampling deferred to the OTel Collector, stdout + OTLP shipping — see [ADR-0012](decisions/0012-observability-pino-opentelemetry.md). - **Audit trail:** dedicated `audit.events` schema in the same Postgres instance, append-only by Postgres role grants (`audit_writer` INSERT, `audit_reader` SELECT, `audit_archiver` DELETE older than retention; no `UPDATE`/`TRUNCATE` to anyone); 365-day retention default; cross-referenced with app logs via `trace_id` and `actor_id_hash` (same salt); blocking writes (no audit ⇒ no action) — see [ADR-0013](decisions/0013-audit-trail-separated-postgres-append-only.md). - **Downstream API access:** unified `DownstreamApiClient` (`@nestjs/axios` + `cockatiel`), per-service `DownstreamApiConfig`; default auth strategy is **OBO via MSAL Node** for Entra-protected APIs (downstream-scoped tokens cached in Redis with AES-256-GCM under a dedicated key); fallback strategy is service credential + signed `X-User-Assertion` JWT (BFF JWKS at `/.well-known/jwks.json`); per-call audience pre-check; no `axios`/`fetch` outside `src/downstream/` — see [ADR-0014](decisions/0014-downstream-api-access-obo-pattern.md). -- **CI/CD:** **Gitea Actions** (level-2 implementation; will be superseded by a GitLab migration ADR within 6-18 months). Trunk-based with squash-merge, branch protection on `main`, all CI gates blocking. Thin YAML — orchestration logic lives in `package.json` scripts (`ci:check`, `ci:scan`, `ci:commits`) and Nx targets, runnable locally. Gates: format / lint / type-check / test / build / audit / secret-scan / commit-lint, plus future `a11y` and `perf`. Self-hosted `act_runner` on-prem. Conventional Commits validated locally (hook) and in CI (defense in depth). Required reviewer count = 0 in v1, raised to ≥1 once a second contributor joins. Signed commits recommended, revisited at GitLab migration — see [ADR-0015](decisions/0015-cicd-gitea-actions.md). +- **CI/CD:** **Gitea Actions** (level-2 implementation; will be superseded by a GitLab migration ADR within 6-18 months). Trunk-based with squash-merge, branch protection on `main`, all CI gates blocking. Thin YAML — orchestration logic lives in `package.json` scripts (`ci:check`, `ci:scan`, `ci:commits`) and Nx targets, runnable locally. Gates: format / lint / type-check / test / build / audit / secret-scan / commit-lint, plus `a11y` (per ADR-0016) and future `perf`. Self-hosted `act_runner` on-prem. Conventional Commits validated locally (hook) and in CI (defense in depth). Required reviewer count = 0 in v1, raised to ≥1 once a second contributor joins. Signed commits recommended, revisited at GitLab migration — see [ADR-0015](decisions/0015-cicd-gitea-actions.md). +- **Accessibility:** **WCAG 2.2 AA baseline + targeted AAA** on criteria with high impact for APF's user base (1.4.6 Contrast Enhanced, 2.2.3 No Timing, 2.3.3 Animation, 3.1.5 Reading Level, 1.4.8 Visual Presentation, 2.4.9 Link Purpose, 3.3.5 Help). RGAA 4.1 alignment for French audit. UI stack: **Angular CDK + spartan-ng + Tailwind** (no Angular Material; no React libs). User-preferences panel (contrast / text size / motion / spacing / cognitive simplification / reading focus) persisted in session. Tooling: `@angular-eslint/template/*` lint, `@axe-core/playwright` e2e (blocking on critical/serious), token-contrast CI check, touch-target check (44×44 min). Manual testing cadence with APF's internal user panel before each major release. Public accessibility statement page at `/accessibility` and `/accessibilite` — see [ADR-0016](decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md). - **Local quality gates:** Husky + lint-staged + commitlint with Conventional Commits — see [ADR-0007](decisions/0007-pre-commit-hooks-and-conventional-commits.md). - **Runtime:** Node.js latest LTS major. diff --git a/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md b/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md new file mode 100644 index 0000000..f211ad4 --- /dev/null +++ b/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md @@ -0,0 +1,244 @@ +--- +status: accepted +date: 2026-04-30 +decision-makers: R&D Lead +tags: [accessibility, frontend, process] +--- + +# Accessibility baseline — WCAG 2.2 AA + targeted AAA, Angular CDK + spartan-ng + Tailwind, APF panel testing + +## Context and Problem Statement + +The host organisation is **APF France Handicap**. Accessibility is the organisation's core mission, not a compliance checkbox. A meaningful proportion of the portal's users will be in situations of disability — visual, motor, cognitive, hearing — and will rely on assistive technologies (screen readers, switch controls, eye tracking, voice control, head pointers, mouth sticks). The portal's accessibility quality is therefore a **product attribute**, not a downstream concern. + +The default WCAG 2.2 AA baseline applicable to enterprise portals would *under-serve* this user base. We need to fix: +- the conformance target (WCAG level + targeted enhancements); +- the implementation stack (component library, primitives, styling); +- the user-preferences surface (contrast, text size, motion, etc.) the portal must offer; +- the testing strategy, including manual testing with users from APF's internal network; +- the CI gates that prevent regression; +- the legal-disclosure surface (the EU Accessibility Act–required accessibility statement). + +This ADR fixes the framework. Concrete component-by-component implementation lands as the scaffold and features arrive. + +## Decision Drivers + +* APF's mission elevates accessibility from "compliance" to "product". +* European Accessibility Act (EAA, in application since June 2025) makes WCAG 2.2 AA a legal floor in the EU; the portal will publish an accessibility statement and accept regression as a defect, not as a backlog item. +* RGAA 4.1 (French national standard, derived from WCAG) is the operational reference for accessibility audits in France; alignment is necessary to be auditable by French experts (including APF's own). +* The user base extends beyond screen-reader users: switch controls, eye tracking, voice control, cognitive load tolerance — all must be considered. +* The technical stack is locked on Angular ([ADR-0004](0004-frontend-stack-angular-csr-zoneless-signals.md)). Component-library choice must therefore be Angular-native or framework-agnostic. +* The dual-audience design ([ADR-0008](0008-identity-model-entra-workforce-dual-audience.md)) means a11y must be uniform across audiences — workforce and (future) customer users alike. +* Anti-bricolage: no hand-rolled a11y where a maintained library exists. + +## Considered Options + +### Conformance target +* WCAG 2.2 **AA** baseline (EU EAA legal floor). +* WCAG 2.2 **AA + targeted AAA** on criteria with high impact for APF's user base. (Chosen.) +* WCAG 2.2 **AAA** uniform. + +### UI component stack +* Angular Material (Angular CDK + Material visual layer). +* **Angular CDK + spartan-ng + Tailwind.** (Chosen — see also `notes/argumentaire-stack-ui-spartan-cdk-tailwind.md` for the team-internal rationale.) +* Custom-on-Angular-CDK only (no spartan-ng). +* React-ecosystem libs (shadcn/ui, dice-ui, animate-ui). + +### User-preferences scope +* None (rely on browser/OS prefs). +* `prefers-reduced-motion` only. +* **Full preferences panel** — contrast modes, text size, motion, spacing, simplified UI. (Chosen.) + +### Testing strategy +* Automated only (axe-core + lint). +* Automated + external annual audit. +* **Automated + manual screen-reader/keyboard/switch testing + APF user panel cadence + annual internal audit.** (Chosen.) + +### Documentation surface +* Internal docs only. +* **Public accessibility statement page (legal EAA requirement) + internal patterns library.** (Chosen.) + +## Decision Outcome + +### Conformance target + +**WCAG 2.2 AA** as the universal baseline. **AAA** applied on the criteria that materially affect APF's user base: + +| AAA criterion | What it adds vs AA | Why APF | +| --- | --- | --- | +| **1.4.6 Contrast (Enhanced)** | 7:1 normal text / 4.5:1 large text (vs 4.5:1 / 3:1 at AA) | Low-vision users are a primary audience | +| **2.2.3 No Timing** | No time limits except where essential | Cognitive load and motor speed vary widely | +| **2.3.3 Animation from Interactions** | All interaction-driven animation can be disabled | Vestibular and cognitive disorders | +| **3.1.5 Reading Level** | Content readable at lower secondary level *or* a simpler alternative is provided | Cognitive accessibility | +| **1.4.8 Visual Presentation** | User control over text formatting (line spacing, paragraph spacing, line length) | Dyslexia, low vision | +| **2.4.9 Link Purpose (Link Only)** | Each link's purpose understandable from its text alone (no surrounding context) | Screen-reader navigation by link list | +| **3.3.5 Help** | Context-sensitive help available where needed | Cognitive accessibility | + +The remaining AAA criteria (e.g. 1.2.6 Sign Language, 1.2.8 Media Alternative) are **not** uniformly required — they apply where the corresponding content type appears, and are addressed case by case. **RGAA 4.1** alignment is maintained as the audit reference for French experts (APF, CNIL audits, public-sector partners). + +### User-preferences panel + +The portal exposes a **first-class user-preferences panel** accessible from any page (keyboard-discoverable, screen-reader-announced, persisted in the user's session per [ADR-0010](0010-session-management-redis.md)): + +| Preference | Options | Default | +| --- | --- | --- | +| Contrast mode | Standard (AA-compliant) / Enhanced (AAA, 7:1) / Maximum (high-contrast OS-style, monochrome) | Standard | +| Text size | 100 % / 125 % / 150 % / 175 % / 200 % | 100 % | +| Motion | Full / Reduced / None | inherited from `prefers-reduced-motion` | +| Text spacing | Default / Generous (line-spacing 1.8, paragraph-spacing 2×, letter-spacing 0.12em) | Default | +| Cognitive simplification | Off / On (reduces secondary chrome, larger primary actions, easier-to-read summaries when authored) | Off | +| Reading focus | Off / Line-by-line | Off | + +Preferences are saved per user; an unauthenticated default is read from browser preferences (`prefers-reduced-motion`, `prefers-contrast`, etc.). Changing a preference takes effect immediately, no page reload. + +### Component stack + +**Angular CDK + spartan-ng + TailwindCSS**. See [`notes/argumentaire-stack-ui-spartan-cdk-tailwind.md`](../notes/argumentaire-stack-ui-spartan-cdk-tailwind.md) for the full rationale produced for the dev team. Summary: + +- **Angular CDK** (`@angular/cdk`) — primitives accessibility layer maintained by Google's Angular Material team. Equivalent role to Radix UI in React. Provides `Overlay`, `FocusTrap`, `FocusMonitor`, `LiveAnnouncer`, `A11yModule`, `ListKeyManager`, `Drag and Drop`, etc. Battle-tested since 2017. +- **spartan-ng** (https://www.spartan.ng/) — copy-paste component layer built on Angular CDK, styled with Tailwind. Components are checked into our repo (`libs/shared/ui/`) and modified freely. Equivalent role to shadcn/ui in React. +- **Tailwind CSS** — utility-first styling. Tokens for colour, spacing, contrast tiers live in `libs/shared/tokens/`. The contrast tiers map directly to the user preference modes above. + +Angular Material is **rejected** because its visual design system (Material Design) is opinionated in ways that clash with both APF branding and the multi-tier contrast requirements. React-ecosystem libraries (shadcn/ui, dice-ui, animate-ui) are rejected as incompatible with the Angular stack ([ADR-0004](0004-frontend-stack-angular-csr-zoneless-signals.md)). + +### Concrete patterns to enforce + +| Concern | Rule | +| --- | --- | +| Sémantique HTML | `
`, `
`, `