OSS-first docs

These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.

Accessibility & WCAG Compliance — **specs.md**

**Goal:** Ship interfaces that are usable by everyone, by default. This spec sets non‑negotiable rules, checklists, and CI gates to meet **WCAG 2.2 AA** (aim for AAA where low‑cost), align with **EN 301 549** (EU), and keep parity on **web (Next.js)** and **mobile (Expo/React Native)**.
field.key.label
Accessibility & WCAG Compliance — **specs.md**
field.version.label
field.type.label
field.title.label
Accessibility & WCAG Compliance — **specs.md**
field.description.label
**Goal:** Ship interfaces that are usable by everyone, by default. This spec sets non‑negotiable rules, checklists, and CI gates to meet **WCAG 2.2 AA** (aim for AAA where low‑cost), align with **EN 301 549** (EU), and keep parity on **web (Next.js)** and **mobile (Expo/React Native)**.
field.tags.label
accessibility_wcag_compliance_specs
field.owners.label
field.stability.label
public

**Goal:** Ship interfaces that are usable by everyone, by default. This spec sets non‑negotiable rules, checklists, and CI gates to meet **WCAG 2.2 AA** (aim for AAA where low‑cost), align with **EN 301 549** (EU), and keep parity on **web (Next.js)** and **mobile (Expo/React Native)**.

---

0) Scope & Principles

**Standards:** WCAG 2.2 AA (incl. new 2.2 SCs: Focus Not Obscured, Focus Appearance, Target Size, Dragging Movements, Accessible Authentication, Redundant Entry). EN 301 549 compliance by conformance to WCAG.

**Principles:** Perceivable, Operable, Understandable, Robust (POUR).

**Rule of ARIA:** “No ARIA is better than bad ARIA.” Prefer native elements.

**Definition of Done (DoD):** No Critical/Major a11y issues in CI; keyboard complete; SR (screen reader) smoke test passed; contrasts pass; acceptance criteria below satisfied.

**Repo Alignment:** Web apps use **Radix Primitives + shadcn** wrappers centralized in `packages/lssm/libs/ui-kit-web`. Prefer those components over per‑app duplicates (`packages/*/apps/*/src/components/ui`). When missing, add to `ui-kit-web` first, then adopt app‑side.

---

1) Design Requirements (Design System & Tokens)

**1.1 Color & Contrast**

Body text, icons essential to meaning: **≥ 4.5:1**; large text (≥ 18.66px regular / 14px bold): **≥ 3:1**.

Interactive states (default/hover/active/disabled/focus) must maintain contrast **≥ 3:1** against adjacent colors; text within components follows text ratios.

Provide light & dark themes with tokens that guarantee minimums. **Never rely solely on color** to convey meaning; pair with text, shape, or icon.

**1.2 Focus Indicators (WCAG 2.4.11/12)**

Every interactive element has a **visible focus** with clear offset; indicator contrast **≥ 3:1** vs adjacent colors and indicator **area ≥ 2 CSS px** thick.

Focus **must not be obscured** by sticky headers/footers or scroll containers.

**1.3 Motion & Preferences**

Respect `prefers-reduced-motion`: suppress large parallax, auto‑animations; provide instant alternatives.

Avoid motion that could trigger vestibular issues; under PRM, use fade/scale under **150ms**.

**1.4 Target Size (2.5.8)**

Hit areas **≥ 24×24 CSS px** (web) and **≥ 44×44 dp** (mobile) unless exempt.

**1.5 Typography & Layout**

Support zoom to **400%** without loss of content/functionality; responsive reflow at **320 CSS px** width.

Maintain clear heading hierarchy (h1…h6), one **h1** per view.

Repository baseline (Web): default body text uses Tailwind `text-lg` (≈18px). As of 2025‑09‑20, the repository bumped all Tailwind typography scale usages by +1 step (e.g., `text-sm`→`text-base`, `text-base`→`text-lg`, …, `text-8xl`→`text-9xl`). For long‑form content, default to `prose-lg`.

Do not use `text-xs` for body copy. Reserve `text-sm` only for non‑essential meta (timestamps, fine print) while ensuring contrast and touch targets remain compliant.

When increasing font size, ensure line height supports readability. Prefer Tailwind defaults or `leading-relaxed`/`leading-7` for body text where dense blocks appear.

**1.6 Iconography & Imagery**

Decorative images: `alt=""` or `aria-hidden="true"`.

Informative images: concise, specific **alt**; complex charts require a **data table or long description**.

---

2) Content Requirements (UX Writing)

Links say **what happens** (avoid “click here”).

Buttons start with verbs; avoid ambiguous labels.

Form labels are **visible**; placeholders are **not labels**.

Error messages: human + programmatic association; avoid color‑only.

Authentication: allow **copy/paste**, password managers, and avoid cognitive tests alone (**3.3.7/3.3.8/3.3.9**).

Avoid CAPTCHAs that block users; if unavoidable, provide **multiple alternatives** (logic-free).

---

3) Engineering Requirements (Web — Next.js/React)

Use and extend `packages/lssm/libs/ui-kit-web` as the default UI surface. It wraps **Radix** primitives with sensible a11y defaults (focus rings, roles, keyboard, ARIA binding). When a gap exists, add it to `ui-kit-web` first.

**3.1 Semantics & Landmarks**

Use native elements: `<button>`, `<a href>`, `<label for>`, `<fieldset>`, `<legend>`, `<table>`, etc.

Landmarks per page: `header`, `nav`, `main`, `aside`, `footer`. Provide a **Skip to main content** link.

Provide a **Route Announcer** (`aria-live="polite"`) and move focus to page **h1** after navigation.

**3.2 Keyboard**

All functionality available with keyboard alone. Tab order follows DOM/visual order; **no keyboard traps**.

Common bindings:

Space/Enter → activate button; Enter on link;

Esc closes dialogs/menus;

Arrow keys for lists/menus/tablists with **roving tabindex**.

**3.3 Focus Management**

On route change (Next.js), move focus to the page `<h1>` or container and announce via a live region.

Dialogs/menus: **trap focus** inside; return focus to invoking control on close.

Don’t steal focus except after explicit user action.

**3.4 Forms**

Each input has a `<label>` or `aria-label`. Group related inputs with `<fieldset><legend>`.

Associate errors via `aria-describedby` or inline IDs; announce with `role="alert"` (assertive only for critical).

Provide **autocomplete** tokens for known fields; show **inline validation** and do not block on **onBlur** alone.

**3.5 ARIA Usage**

Only when needed; match patterns (dialog, menu, combobox, tablist, listbox) per ARIA Authoring Practices.

Ensure **name/role/value** are programmatically determinable.

**3.6 Media**

Videos: **captions**; provide **transcripts** for audio; audio descriptions for essential visual info.

No auto‑playing audio. Auto‑playing video must be muted and pausable; provide controls.

**3.7 Tables & Data**

Use `<th scope>` for headers; captions via `<caption>`; announce sorting via `aria-sort`.

Provide CSV/JSON export where charts are primary.

**3.8 Performance & Robustness**

Avoid content shifts that move focus; reserve space or use skeletons.

Maintain accessible names through hydration/SSR; avoid `dangerouslySetInnerHTML` where possible.

**3.9 Next.js specifics**

Use `next/link` for navigation; ensure links are **links**, not buttons.

`next/image` must include **alt** (empty if decorative).

Announce route changes with a **global live region** and shift focus to the new view.

**3.10 Accessibility library integration**

Import `@lssm-tech/lib.accessibility` at app root. It auto-imports its `styles.css` via the package entry; ensure bundlers keep CSS side effects. If your app tree-shakes CSS, explicitly import the stylesheet once in your root layout:

// app/layout.tsx
import '@lssm-tech/lib.accessibility'; // includes tokens and provider exports
// or if needed: import '@lssm-tech/lib.accessibility/src/styles.css';

Wrap the app with `AccessibilityProvider` and include an element with `id="main"` for the skip link target.

---

3b) lssm/ui-kit-web — Component Patterns & Defaults

Source: `packages/lssm/libs/ui-kit-web/ui/*`

**Button/Input/Textarea**: Built‑in `focus-visible` rings; ensure visible labels via `FormLabel` or `aria-label`.

**Form** (`form.tsx`): `FormControl` wires `aria-invalid` and `aria-describedby` to `FormMessage` and `FormDescription`. Prefer `FormMessage` for inline errors. Add `role="alert"` only for critical.

**Dialog/Sheet/Dropdown**: Use Radix wrappers for focus‑trap and return‑focus. Provide `DialogTitle` + `DialogDescription` for name/description.

**Select/Combobox**: Prefer `SelectTrigger` with visible label; for icon‑only triggers, supply `aria-label`. Document examples in each app.

**Tabs**: Use `TabsList`, `TabsTrigger`, `TabsContent`; names are programmatically determinable.

**Toast/Toaster**: Prefer non‑blocking announcements; map critical to assertive region; include action buttons with clear labels.

**Table**: Use `TableCaption`; ensure `TableHead` cells use proper `scope`. Provide `aria-sort` on sortable headers.

**Utilities to add (repo action)**:

`SkipLink` component and pattern in layouts.

`RouteAnnouncer` (`aria-live="polite"`) and **FocusOnRouteChange** helper.

`VisuallyHidden` wrapper (Radix visually-hidden or minimal utility).

`useReducedMotion` helper; honor in animated components.

Touch‑size variants (≥44×44) for interactive atoms.

---

4) Engineering Requirements (Mobile — Expo/React Native)

Set `accessibilityLabel`, `accessibilityHint`, and `accessibilityRole` on touchables.

Ensure **hit slop** / min size **≥ 44×44 dp**.

Support Dynamic Type / font scaling; no clipped text at **200%**.

Respect **Invert Colors** and **Reduce Motion**; avoid flashing.

Group related items with `accessibilityElements` ordering; hide decoration with `accessible={false}` or `importantForAccessibility="no-hide-descendants"` when appropriate.

Test with **VoiceOver (iOS)** and **TalkBack (Android)**.

---

5) Component Patterns (Acceptance Rules)

**Buttons & Links**

Use `<button>` for actions, `<a href>` for navigation. Provide disabled states that are perceivable beyond color.

**Navigation**

Provide **Skip link**. One primary nav landmark. Indicate current page (`aria-current="page"`).

**Menus/Combobox/Autocomplete**

Follow ARIA patterns: focus moves into list; `aria-expanded`, `aria-controls`, `aria-activedescendant` when applicable; Esc closes; typing filters.

**Modals/Dialogs**

`role="dialog"` or `alertdialog` with **label**; focus trapped; background inert; Esc closes; return focus to trigger.

**Tabs**

`role="tablist"`; tabs are in the tab order; arrow keys switch focus; content is `role="tabpanel"` with `aria-labelledby`.

**Toasts/Notifications**

Non-critical: `aria-live="polite"`; critical: `role="alert"` sparingly.

**Infinite Scroll / “Load More”**

Provide **Load more** control; announce new content to SR; preserve keyboard position.

**Drag & Drop (2.5.7)**

Provide **non‑drag** alternative (e.g., move up/down buttons).

**Charts & Maps**

Provide **table alternative** or textual summary; keyboard access to datapoints where interactive.

---

6) Testing & CI (Blocking Gates)

**Static & Unit**

`eslint-plugin-jsx-a11y` — error on violations.

`jest-axe` — unit tests for components.

**Automated Integration**

`axe-core` via Playwright or Cypress on critical flows.

`pa11y-ci` on key routes; threshold: **0 Critical / 0 Serious** to merge.

Lighthouse CI a11y score **≥ 95** on target pages.

**Manual QA (per release)**

**Keyboard patrol:** navigate primary flows without mouse.

**Screen reader smoke:** NVDA (Windows) or VoiceOver (macOS/iOS) across login, navigation, forms, dialogs.

**Zoom & Reflow:** 200–400% & 320 px width.

**Color/Contrast check:** tokens in both themes.

**Reporting**

A11y issues labeled: `a11y-blocker`, `a11y-bug`, `a11y-enhancement` with WCAG ref.

---

7) Repository‑Specific Adoption Plan

Centralize UI usage on `packages/lssm/libs/ui-kit-web` and de‑duplicate per‑app `components/ui` where feasible.

Introduce `SkipLink`, `RouteAnnouncer`, `FocusOnRouteChange`, and `VisuallyHidden` in `ui-kit-web`. Adopt in app layouts (`app/layout.tsx`) first.

Add `useReducedMotion` and wire into animated components (e.g., `drawer`, `tooltip`, `carousel`).

Add touch‑size variants to `Button`, `IconButton`, `TabsTrigger`, toggles.

Document Select label patterns and error association in Forms.

---

8) Code Snippets

**Skip Link**

<a
  class="sr-only focus:not-sr-only focus-visible:outline focus-visible:ring-4 focus-visible:ring-offset-2"
  href="#main"
  >Skip to main content</a
>
<main id="main">…</main>

**Dialog (Radix + shadcn/ui) — essentials**

// Ensure label, description, focus trap, and return focus on close remain intact
<Dialog>
  <DialogTrigger asChild>
    <button aria-haspopup="dialog">Open settings</button>
  </DialogTrigger>
  <DialogContent aria-describedby="settings-desc">
    <DialogTitle>Settings</DialogTitle>
    <p id="settings-desc">Update your preferences.</p>
    <DialogClose asChild>
      <button>Close</button>
    </DialogClose>
  </DialogContent>
</Dialog>

**Form error association**

<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" aria-describedby="email-err" />
<p id="email-err" role="alert">Enter a valid email.</p>

**Route change announcement (Next.js)**

// Add once at app root
<div
  aria-live="polite"
  aria-atomic="true"
  id="route-announcer"
  className="sr-only"
/>

---

9) Exceptions & Waivers

If a criterion cannot be met, file an issue with: context, attempted alternatives, WCAG reference, impact assessment, and a remediation date. **Temporary waivers only.**

---

10) Ownership

**Design:** maintains token contrast, component specs.

**Engineering:** enforces CI gates, implements patterns.

**QA:** runs manual checks per release.

**PM:** blocks release if AA not met on user‑visible flows.

---

11) References (internalize; no external dependency at runtime)

WCAG 2.2 (AA), EN 301 549. ARIA Authoring Practices. Platform HIG (Apple, Material).

`packages/lssm/libs/ui-kit-web` as the canonical UI source for web.

**Bottom line:** Shipping means **accessible by default**. We don’t trade a11y for speed; we bake it into speed.

---

12) Adoption Status (2025-09-23)

web-artisan: AccessibilityProvider integrated; sr-only/forced-colors applied; 44x44 targets; forms announce errors; jest-axe and cypress-axe in place.

web-strit: AccessibilityProvider integrated; forced-colors, sr-only; forms announce errors; 44x44 targets; contrast tokens and text-scale wired; jest-axe and cypress-axe in place.

web-coliving: AccessibilityProvider integrated; forced-colors and focus visibility added; text-scale wired; landing pages converted to `Section`/stacks with text-lg defaults; CTA capture standardized; ESLint guard for text-xs in main content; jest-axe and cypress-axe in place. Next: audit icon-only controls and ensure 44x44 targets; add role="alert" where critical.

CI gates: run eslint a11y, jest-axe on components, and cypress-axe on critical flows per app.

---

13) CI Hardening & Visual QA

Linting: Run eslint with jsx-a11y rules across all web apps; block on violations.

Unit: Run jest-axe for ui-kit-web and app-level components.

Integration: cypress-axe on key flows (auth, forms, dialogs, tables).

Synthetic scans: pa11y-ci on critical pages (0 Critical/Serious policy).

Performance/A11y audit: Lighthouse CI with a11y score >= 95 on target routes.

Artifacts: Upload pa11y and Lighthouse reports per PR; annotate failures.

Recent additions (2025-09-26)

AutocompleteInput (groceries): Upgraded to ARIA combobox pattern with `aria-controls`, `aria-activedescendant`, `Escape`/`Tab` handling, and labelled listbox.

Cypress a11y tests added for furniture and incidents modules on `/modules` and operators flows; checks run axe with critical/serious impacts.

14) Accessibility Telemetry (PostHog)

Events (anonymized): a11y_pref_changed (text_scale, contrast_mode, reduce_motion), a11y_panel_opened.

Properties: app, route, previous_value, new_value, timestamp.

Dashboards: Adoption over time, per app/route; correlation with reduced bounce on forms.

Privacy: No PII; aggregate only.