LoomWeaver — accessibility (a11y)
Target: WCAG 2.1 Level AA. a11y lives in the core — every weaver that uses the host
vocabulary inherits it automatically (the same way it inherits the permission broker). This file is
the binding guardrail; it complements design-tokens.md (colours/contrast).
What the platform already brings (inherited)
- Landmarks:
<header>(bar) ·<nav>(rail) ·<main>(content) ·<aside>(panel) + a skip-to-content link as the first tab stop. - Focus: visible
focus-visiblering; dialogs have a focus trap + focus restore; popups/menus follow the ARIA menu keyboard pattern (arrow keys/Home/End, Escape closes, focus returns to the trigger). - Live regions: toasts announce with
role="alert"/"status"depending on urgency. - Motion:
prefers-reduced-motionis respected globally (non-essential transitions/animations collapse; the loading spinner stays, as essential status feedback). - Contrast: all semantic tokens are AA-verified (see the token rules below).
- Tab strips: every pane strip is a real
role="tablist"withrole="tab"children. Because ARIA specifiestabas “children presentational”, the × (close) and 📌 (unpin) are not focusable buttons but pure pointer affordances (aria-hidden); the keyboard equivalent isDeleteon the focused tab (announced viaaria-keyshortcuts), plus the tab context menu. - Text size (WCAG 1.4.4): the shell ships a user setting “text size”
(Settings → Options → General) that scales the whole UI through the
:rootfont-size(90/100/112.5/125 %, relative to the browser’s base font). Every distribution inherits it. - Automated net: an axe-core E2E (
apps/loom-testbed-e2e/src/a11y.spec.ts) checks every core screen against WCAG 2.1 A/AA and turns the nightly CI red as soon as a violation appears.
Rules for plugin authors (checklist)
- Use the host vocabulary (
<lw-button>, dialogs viactx.ui.*,<lw-icon>,<lw-markdown>…) — it is already accessible (focus, contrast, keyboard). Your own web component/iframe is an emergency exit only — and it costs more than accessibility work, see your own custom element. - An accessible name for everything interactive: visible text or
aria-label. Icon-only buttons requirearia-label. - Semantic colour tokens only (never raw hex). In particular:
- Brand blue as text →
text-brand-text(nottext-brand, which is AA only as a fill/icon). - Filled action surfaces with a label → use the
<lw-button>variants (they carry the AA-capable*-filltones); do not buildbg-brand+ text yourself.
- Brand blue as text →
- Never rely on colour alone — also convey state through an icon/text/shape.
- Keyboard: everything reachable by Tab; your own menus/popups follow the ARIA pattern (arrow
keys, Escape, focus restore) — never
role="menu"without the keyboard behaviour (a role without its behaviour is worse than no role). - Motion: reduced-motion is inherited; gate your own animations behind the media query too.
- Images: meaningful
alt; purely decorative ones →alt="". - Font sizes in
rem, never inpx. The text-size setting works through the:rootfont-size— sizing text inpxsilently opts out of the user’s choice (and ignores an enlarged browser base font). Tailwind’stext-*utilities are alreadyrem, so: use the utilities and avoid rawpxfont sizes.
Colour token rules (AA)
| Purpose | Token | Rule |
|---|---|---|
| Body text / labels | content / content-muted / content-faint |
all ≥4.5:1 on every surface |
| Brand as text | brand-text |
deeper tone, ≥4.5:1 (not brand) |
| Brand as fill+label | brand-fill (+ on-brand) |
primary-button fill; brand stays the identity (logo/icon) |
| Danger button | negative-fill (+ on-negative) |
deeper than negative; negative stays error text/icon |
| Status icons/text | positive/negative/caution/info |
≥3:1 as an icon; ≥4.5:1 as text |
| Borders/dividers | border |
decorative (exempt from 1.4.11); interaction boundaries additionally carry a focus ring/fill |
Checking
nx e2e loom-testbed-e2eruns the axe net. For a new screen/state, add annew AxeBuilder({ page }).withTags(['wcag2a','wcag2aa','wcag21a','wcag21aa']).analyze()scan (importAxeBuilderfrom@axe-core/playwright) — the existing scans inplatform/apps/loom-testbed-e2e/src/a11y.spec.tsare the template to copy.- axe only covers what a machine can check (~⅓–½): labels, contrast, ARIA, roles. Test focus order, keyboard completeness and meaningful alt text by hand.