diff --git a/apps/web/docs/accessibility.md b/apps/web/docs/accessibility.md new file mode 100644 index 0000000..c5e95fd --- /dev/null +++ b/apps/web/docs/accessibility.md @@ -0,0 +1,231 @@ +# Frontend accessibility guide + +The accessibility standard `apps/web` targets, and the patterns required to meet it: +keyboard navigation through the conversation list and message composer, focus management +in modals and the safety-number panel, screen-reader announcement of incoming messages, +and colour contrast. + +This document describes both the pattern to follow and, honestly, where the current code +already meets it and where it does not yet. Where a gap is called out, treat it as +something to fix when you touch that area, not as the intended design. + +--- + +## Target standard + +**WCAG 2.1 Level AA.** This is the conventional baseline for a web application handling +real user-to-user communication, and is the standard assumed throughout this document — +there is no stricter internal bar and no formal deviation from it. + +**How it is checked today: manually, not automatically.** There is no `eslint-plugin-jsx-a11y`, +no automated axe/Lighthouse run in CI, and no accessibility test suite in this repo as of +this writing. Conformance currently depends entirely on the patterns below being followed +by hand and reviewed in PRs. If you are adding accessibility tooling, wiring an automated +check (axe-core in CI, or `eslint-plugin-jsx-a11y` at minimum) closes a real gap rather +than adding redundant coverage — until then, the [pre-merge checklist](#pre-merge-checklist-for-a-new-interactive-component) +below is the actual enforcement mechanism. + +--- + +## Keyboard navigation + +### Conversation list + +`components/conversations/ConversationListSidebar.tsx` renders each conversation as a +plain ``: + +```tsx + + {/* avatar, title, preview */} + +``` + +There is no custom keyboard handling — no roving `tabindex`, no arrow-key list navigation. +Keyboard support comes entirely from using a real `` (an anchor): it is naturally +focusable, appears in Tab order, and activates on Enter, all for free. **This is the +required pattern for list items in this app** — a clickable row must be a real +link/button, never a `
`, precisely so keyboard support does not have to be +hand-built. Arrow-key roving-tabindex navigation (a full ARIA `listbox`/`menu` pattern) is +not implemented and is not required — sequential Tab order through the list is the +supported navigation model. + +### Message composer + +The composer (`app/app/conversations/[id]/page.tsx`) supports Enter-to-send / +Shift+Enter-for-newline on the message input: + +```tsx + setSendText(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + void handleSendEncrypted(); + } + }} +/> +``` + +When adding a keyboard shortcut like this, always `preventDefault()` only on the branch +that consumes the key (here, plain Enter) and let every other key (including Shift+Enter) +fall through untouched — do not swallow keys you are not handling. + +The composer's send and attach-file buttons are real `