Theme
Accessibility
The OmniBots chat widget is built to meet WCAG 2.1 AA compliance requirements. This page documents the specific accessibility features, keyboard interactions, ARIA patterns, internationalization support, and responsive behavior that ensure the widget is usable by everyone.
Keyboard Navigation
The widget is fully operable via keyboard without requiring a mouse or touchscreen.
Key Bindings
| Key | Action |
|---|---|
| Tab | Moves focus to the next focusable element within the widget |
| Shift + Tab | Moves focus to the previous focusable element |
| Escape | Closes the chat window (returns focus to the launcher) |
| Enter | Sends a message (in the text input), activates buttons and links |
| Shift + Enter | Inserts a newline in the text input |
| Space | Activates buttons and toggles |
Focus Management
Focus behavior follows a predictable pattern to avoid disorientation:
- Opening the widget -- Focus moves to the chat window. If the pre-chat form is shown, focus moves to the first form field. If the chat is active, focus moves to the message input
- Focus trapping -- While the chat window is open, Tab cycles through focusable elements within the widget only. Focus does not leak to elements behind the widget
- Closing the widget -- Focus returns to the launcher button
- Dialog modals -- When the feedback form dialog opens, focus is trapped within it. Pressing Escape closes the dialog and returns focus to the trigger element
Visible Focus Indicators
All interactive elements display a visible focus ring when focused via keyboard:
- Inputs and buttons: 2px solid outline in the theme's primary color with 2px offset
- Focus-visible only: Focus indicators appear only for keyboard navigation (
:focus-visible), not for mouse clicks (:focus:not(:focus-visible)) - High contrast: Focus rings are designed to be visible against both light and dark backgrounds
imageWidget showing visible keyboard focus indicators: 2px primary-color outline on the send button, text input, and a quick reply button, with tab order numbers annotated
Live Regions
The widget uses ARIA live regions to announce dynamic content changes to screen readers.
Message List
The message list container uses role="log" with aria-live="polite". New messages are announced as they arrive without interrupting the user's current action.
Typing Indicator
The typing indicator has role="status" with aria-live="polite", announcing when the bot or agent is typing.
Error Messages
- Connection errors use
role="status"witharia-live="polite" - Form validation errors use
role="alert"for immediate announcement - Upload errors are announced via
role="alert"
Escalation Banner
The escalation banner uses role="status" with aria-live="polite" and aria-atomic="true", ensuring the entire banner content is re-announced when the state changes (connecting, waiting, connected).
Unread Count
The unread message badge on the launcher button is announced to screen readers via appropriate ARIA attributes.
Text Alternatives
All non-text content has text alternatives:
| Element | Technique |
|---|---|
| Bot/agent avatar | aria-hidden="true" (decorative; sender info is in the message label) |
| Icon buttons | aria-label describing the action (e.g., "Attach file", "Send message") |
| SVG icons | aria-hidden="true" on the SVG, descriptive aria-label on the parent button |
| Image attachments | alt attribute with filename (e.g., "Image: photo.jpg") |
| File type icons | aria-hidden="true" (decorative; file info is in adjacent text) |
| Rich content images | alt attribute from image_alt property |
| Quick reply icons | Decorative; the button text serves as the label |
Message Labels
Each message bubble has a computed aria-label that includes:
- Sender identity ("You", "Bot", "Agent", or "System")
- Timestamp
- Message content (Markdown stripped, truncated to 120 characters)
- Attachment count (if any)
Example: "Bot at 2:34 PM: Here are three options for your account. 1 file attached"
Color Contrast
The widget enforces minimum contrast ratios throughout:
Text Contrast (4.5:1 minimum)
| Element | Foreground | Background | Ratio |
|---|---|---|---|
| Body text | #111827 | #FFFFFF | 15.4:1 |
| Muted text | #6B7280 | #FFFFFF | 4.6:1 |
| Recording duration | #DC2626 | #FFFFFF | 4.6:1 |
| Connection status | #B45309 | #FFFFFF | 4.6:1 |
| Error messages | #EF4444 | #FFFFFF | 4.5:1 |
UI Component Contrast (3:1 minimum)
| Component | Technique | Ratio |
|---|---|---|
| Input border | #767F8D on white | 3.2:1 |
| Icon buttons | 1px solid #767F8D border | 3.2:1 |
| Send button | 1px solid rgba(0,0,0,0.2) for light primary colors | 3:1+ |
| Escalation disconnect button | White border on primary background | 3:1+ |
Luminance Detection
The widget includes a luminance detection algorithm for bot bubbles. When a dark bot_bubble_color is configured, the widget automatically switches to light-on-dark color schemes for links, code blocks, blockquotes, and other inline elements.
Resizable Text
The widget supports text resizing up to 200% without loss of functionality:
- Line heights use relative values (e.g.,
1.5) rather than fixed pixel values - Container overflow is set to
autoto allow scrolling when text-spacing increases - The textarea
max-heightis set to120pxwithoverflow-y: auto - Long source titles use wrapping instead of
nowrapto accommodate increased text size - Touch targets maintain a minimum of 24px height (per WCAG 2.5.8)
RTL Support
The widget supports right-to-left (RTL) languages with automatic layout mirroring.
RTL Languages
The following languages trigger RTL layout:
| Language | Code |
|---|---|
| Arabic | ar |
| Hebrew | he |
| Persian | fa |
| Urdu | ur |
When an RTL language is detected (via user selection, browser language, or auto-detection), the widget sets dir="rtl" on its container element. This causes:
- Text alignment to flip to right-to-left
- Flex layouts to reverse direction
- The input area icons to mirror positions
- Quick replies and chips to flow from right to left
RTL detection is performed by the isRTL() function, which checks the base language code (first two characters) against the known RTL language set.
imageSide-by-side comparison of widget in LTR English layout (left-aligned bot bubbles, right-aligned user bubbles, left send button) and RTL Arabic layout (mirrored alignment, right-to-left text flow)
Bundled UI Translations
The widget includes bundled translations for 24 languages, covering all UI labels, button text, status messages, and accessibility strings. These translations load instantly without a network request.
| Language | Code | Language | Code | |
|---|---|---|---|---|
| English | en | Thai | th | |
| Spanish | es | Vietnamese | vi | |
| French | fr | Turkish | tr | |
| Haitian Creole | ht | Swedish | sv | |
| German | de | Indonesian | id | |
| Portuguese | pt | Ukrainian | uk | |
| Italian | it | Hebrew | he | |
| Dutch | nl | Danish | da | |
| Polish | pl | Finnish | fi | |
| Russian | ru | Malay | ms | |
| Japanese | ja | Hindi | hi | |
| Korean | ko | Arabic | ar | |
| Chinese | zh |
Each translation covers the following UI strings:
- Header status (Online, Connecting, Offline)
- Input area (placeholder, send, attach, voice controls)
- Messages (welcome, empty state, typing indicator)
- Escalation (waiting, connected, disconnect)
- Errors (connection, upload)
- Accessibility (window labels, descriptions, button labels)
- Hub (select department, switching to agent)
Server-Side Translation
For languages beyond the 24 bundled translations, the widget supports server-side translation via the backend's translation service. This enables support for 100+ languages. The widget sends a translate_request message over WebSocket and receives translated strings in response.
Language Detection Modes
The widget supports four language detection modes, configured per-bot:
| Mode | Behavior |
|---|---|
auto | Backend detects the visitor's language from their first message |
browser | Uses the browser's navigator.language setting |
user_select | Visitor manually selects their language from a selector |
auto_with_select | Auto-detection with the option for the visitor to override via selector |
When widget_language_selector is true in the language configuration, a globe/language icon appears in the input area. Clicking it opens a language selection panel where the visitor can choose from the supported languages.
The current language priority is: user selection > auto-detected > browser language > configured fallback.
Responsive Design
The widget adapts its layout and behavior based on screen size and device capabilities.
Mobile (< 480px)
- Widget expands to fullscreen overlay
- Font sizes increase to 16px in inputs (prevents iOS Safari zoom)
- Touch targets increase to minimum 44px for easy tapping
- Remove buttons on file previews are always visible (no hover required)
- Avatar sizes reduce from 32px to 28px to save space
- Input card border radius reduces from 16px to 12px
- Safe area insets are applied for notched devices
Safe Area Insets
The widget respects safe-area-inset-* environment variables on notched devices (e.g., iPhone with Dynamic Island):
css
@supports (padding-bottom: env(safe-area-inset-bottom)) {
@media screen and (max-width: 480px) {
.input-area {
padding-bottom: calc(12px + env(safe-area-inset-bottom));
}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
This ensures the input area is not obscured by the device's home indicator or notch.
Tablet
On tablet-sized screens, the widget uses its default floating window layout with configurable dimensions. The max_height_percent setting (default 90%) prevents the widget from exceeding the viewport height.
Landscape Mode
In landscape orientation on mobile devices, the widget adjusts its layout to use the available horizontal space while respecting safe area insets on both sides.
Reduced Motion
The widget respects the prefers-reduced-motion media query. When enabled:
| Element | Normal Behavior | Reduced Motion |
|---|---|---|
| Recording mic button | Pulsing scale animation | Static (no animation) |
| TTS stop button rings | Pulsing expansion animation | Static |
| Spinner (typing, loading) | Rotating animation | Static or no animation |
| Dropdown transitions | Slide + fade animation | Instant appearance |
| Content expand/collapse | Smooth height transition | Instant |
| Submit button hover | TranslateY lift | No movement |
| Chevron rotation (sources) | Smooth rotation | Instant |
css
@media (prefers-reduced-motion: reduce) {
.icon-btn.recording {
animation: none;
}
.dropdown-enter-active,
.dropdown-leave-active {
transition: none;
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
High Contrast Mode
The widget responds to prefers-contrast: high:
- Form input borders increase from 1px to 2px width
- Error messages use bold (
font-weight: 600) text - Focus indicators remain the standard 2px with offset for clarity
Screen Reader Testing
The widget has been tested with assistive technologies to ensure a consistent experience. Key interaction patterns include:
- Launcher button announces as a button with the chat window's expanded/collapsed state
- Message list announces as a log region; new messages are read as they arrive
- Pre-chat form announces field labels, required status, and validation errors
- Quick replies announce as buttons with their action labels
- Rich content elements include appropriate ARIA roles and labels
- Escalation state changes are announced via the banner's live region
- File upload progress is announced via
role="progressbar"witharia-valuenow
Widget Container
The widget container uses role="none" so assistive technology looks through to the actual interactive elements inside. The container mirrors aria-expanded to indicate whether the chat window is open or closed.
