The embeddable widget targets WCAG 2.2 AA (FEAT-013). This page records what
is enforced automatically, what must be checked by hand, and the result of the
last manual pass.
The widget renders in whichever scheme the visitor's system asks for, or in the
one an embedder pins with data-color-scheme on the mount element, so every
check below applies to both.
make js-test fails on any of these.
| Check | Where |
|---|---|
| axe-core has no violations for the idle widget, the booking form, and the failure state | frontend/tests/accessibility.test.tsx |
| Focus returns to the launcher on close and to the composer on open | frontend/tests/accessibility.test.tsx |
| Escape closes the widget from inside the shadow root | frontend/tests/accessibility.test.tsx |
The transcript is a polite log and reports aria-busy while a reply is pending |
frontend/tests/accessibility.test.tsx |
| Every message carries a speaker name for a non-sighted listener | frontend/tests/accessibility.test.tsx |
| Every color pair the widget paints meets the AA ratio for its use, in both schemes | frontend/tests/contrast.test.ts |
| Consent is required and explained before contact details are sent | frontend/tests/privacy.test.tsx |
| Details already given are not requested twice (§3.3.7) | frontend/tests/privacy.test.tsx |
| The widget renders no markup into the host document | frontend/tests/widget.test.tsx |
| An unopened panel takes no focus from the embedding page | frontend/tests/accessibility.test.tsx |
| The operator console has no axe violations | frontend/tests/admin.test.tsx |
axe cannot judge color-contrast, target-size, or scrollable-region-focusable
under jsdom, which has no layout engine. Contrast is covered by the ratio test
above; target size and the focusable transcript are covered by the minimum
dimensions in frontend/src/widget/widget.css and the tabindex in
components/Transcript.tsx, and by the manual pass below.
Repeat this list whenever the widget shell, the booking form, or the privacy panel changes. Record the date and result at the bottom.
- Tab from the host page into the widget; confirm the focus ring is visible on every stop and never invisible against its background.
- Reach the launcher, activate it with Enter and with Space; the composer takes focus both times.
- Press Escape from the composer, from a quick action, and from inside the booking form; the widget closes and the launcher takes focus.
- Send a message with Enter from the composer.
- Tab into the transcript and scroll it with the arrow keys.
- Open the privacy panel with the keyboard and reach the delete control.
- Complete a booking without touching the pointer, including the consent checkbox with Space.
- Submit the booking without consent; focus lands on the checkbox and the error is reachable.
Run with VoiceOver (Safari) and NVDA (Firefox).
- The launcher announces its name and expanded state.
- Opening announces the dialog with the assistant's name.
- A new assistant message is announced without moving focus.
- "Waiting for the assistant to reply" is announced when a turn is in flight.
- Each bubble announces its speaker before its text.
- The consent checkbox announces the full statement, including the company name.
- A booking error is announced as an alert.
- The backend-unavailable state is announced as an alert.
- 320 px wide: no horizontal scrolling, the widget fills the viewport, and all controls stay reachable.
- 400 px tall landscape: the widget goes full screen rather than clipping.
- 200% browser zoom on a desktop viewport: no content is lost or overlapped.
- With "reduce motion" enabled at the OS level, the transcript jumps rather than smooth-scrolls.
- Windows high-contrast mode: the window, launcher, and booking card keep a visible boundary.
- Embed on a page whose
bodysetsfont-family,line-height,color, and* { box-sizing: content-box }; the widget is unaffected. - Embed on a page with an element id of
messagesorcomposer; neither page nor widget breaks. - Confirm the host page's own styles are unchanged with the widget mounted.
Date: 2026-08-02, Chromium at 1280×800, 740×400, and 320×568.
Passed:
- Tab order enters the widget in visual order; the focus ring is visible on the quick actions against white and on the close button against the dark header.
- Escape closes the widget and returns focus to the launcher; opening returns focus to the composer.
- 320×568 and 740×400: the widget takes the full viewport, the transcript and composer both stay usable, and neither the page nor the widget scrolls horizontally.
- Every control measures at least 24×24 CSS px (§2.5.8); the smallest is the inline privacy link at 26 px tall.
- Host isolation: with the embedding page forcing
font-family,font-size,line-height,color,letter-spacing, andbox-sizingthrough!importantonbodyand*, and declaring its own#messages,#composer, and#chatInputelements, nothing reached the widget and neither side broke.
Outstanding:
- Screen-reader list (items 1–8) — no VoiceOver or NVDA session has been run against this build.
- Reduced motion and Windows high-contrast mode are implemented in the stylesheet but have not been observed under those OS settings.
- 200% browser zoom was not exercised directly; the 320 px viewport covers the equivalent layout width but not text-only reflow.