Skip to content

a11y: ARIA, focus trap, keyboard nav, WCAG 2.1 AA (#10)#31

Merged
PAMulligan merged 14 commits into
mainfrom
10-accessibility-aria-labels-focus-trap-and-keyboard-navigation
Apr 16, 2026
Merged

a11y: ARIA, focus trap, keyboard nav, WCAG 2.1 AA (#10)#31
PAMulligan merged 14 commits into
mainfrom
10-accessibility-aria-labels-focus-trap-and-keyboard-navigation

Conversation

@PAMulligan
Copy link
Copy Markdown
Contributor

Summary

Brings the Claudius chat widget to WCAG 2.1 AA compliance. Closes #10.

  • Dialog semanticsChatWindow is now role="dialog" with aria-labelledby pointing at the header <h2>. aria-modal="true" is set only on mobile, where the scrim actually blocks the background (honest ARIA claim).
  • Keyboard control — Escape closes the widget (ignored during IME composition). Enter submits (pre-existing). Tab/Shift+Tab cycle via a new useFocusTrap hook. Input autofocuses on open; focus returns to the toggle button on close (pre-existing).
  • Screen-reader announcements — Replaced the noisy role="log" aria-live="polite" container with a dedicated sr-only aria-live="polite" aria-atomic="true" region that announces only the latest assistant message. Markdown markers and long URLs are stripped for SR clarity via a new stripAnnouncementFormatting helper.
  • Contrast — Audited the Tailwind palette; bumped header subtitle from text-white/90 (4.49:1, fails) to text-white (5.17:1).
  • Lint enforcement — Added eslint-plugin-jsx-a11y with recommended rules plus 5 escalated to error. aria-role is configured with ignoreNonDOM: true to handle the ChatMessage component's domain role prop without line-level disables.

Test count: 135 → 154. Lint: 0 errors. Lighthouse accessibility: 97/100 (the sole flag is landmark-one-main on the Vite dev harness index.html, not on the widget component itself).

Test plan

  • cd widget && pnpm install && pnpm test — all 154 tests pass
  • cd widget && pnpm lint — 0 errors
  • cd widget && pnpm dev then at http://localhost:5173:
    • Tab reaches the toggle button with a visible focus ring
    • Enter opens the widget; focus lands on the message input
    • Tab cycles input → send → close → input (stays inside dialog)
    • Shift+Tab wraps backward the same way
    • Escape closes the widget; focus returns to the toggle button
  • NVDA / VoiceOver spot-check (human-only):
    • Opening announces "Chat, dialog"
    • Sending a message — assistant reply is announced once, politely
    • No redundant announcements from the log region (silent by design; only the dedicated live region speaks)

🤖 Generated with Claude Code

@PAMulligan PAMulligan linked an issue Apr 16, 2026 that may be closed by this pull request
@PAMulligan PAMulligan self-assigned this Apr 16, 2026
@PAMulligan PAMulligan added enhancement New feature or request widget Chat widget UI and embed functionality performance Performance improvements labels Apr 16, 2026
@PAMulligan PAMulligan modified the milestone: v1.1.0 Apr 16, 2026
@PAMulligan PAMulligan merged commit 3aff658 into main Apr 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request performance Performance improvements widget Chat widget UI and embed functionality

Projects

Development

Successfully merging this pull request may close these issues.

Accessibility: ARIA labels, focus trap, and keyboard navigation

1 participant