Accessibility
The widget is built to comply with WCAG 2.2 AA. This page lists what the widget does for you and what your integration must keep respecting.
Compliance per criterion
| WCAG criterion | Implementation |
|---|---|
| 1.4.3 Minimum contrast | Light/dark tokens with contrast ≥ 4.5:1 on text and 3:1 on UI components. |
| 1.4.10 Reflow | Responsive layout without horizontal scroll at 320 px. |
| 1.4.13 Content on hover/focus | Tooltips dismissible with Esc and persistent until blur. |
| 2.1.1 Keyboard | The full UI is keyboard-accessible. No traps. |
| 2.1.2 No keyboard trap | Modals trap focus with Esc escape and focus restoration. |
| 2.4.3 Focus order | Logical order: input → toolbar (voice/image) → results → filters. |
| 2.4.7 Focus visible | Focus rings using --nrn-primary at 4 px. |
| 3.2.2 On input | Submit does not fire until the user presses Enter or the button. |
| 3.3.2 Labels | Every input has an aria-label or an associated <label>. |
| 4.1.2 Name, role, value | combobox, listbox, option, dialog, status correctly applied. |
| 4.1.3 Status messages | State changes announced with role="status" (results loaded, filters applied). |
Shadow DOM and assistive tools
The widget mounts in a Shadow DOM in open mode. Modern screen readers (NVDA, JAWS, VoiceOver, TalkBack) navigate correctly across the Shadow Tree nodes and the host document.
Keyboard navigation
| Key | Action |
|---|---|
Tab / Shift+Tab | Move focus across widget elements. |
Enter | Trigger search with the input query. Activates buttons and links. |
Esc | Close any open modal/drawer/dropdown. If nothing is open, clears the query. |
↑ / ↓ | Navigate suggestions and results. |
← / → | Navigate carousels (top products, comparison, kit). |
Cmd+K / Ctrl+K | Optional shortcut to open the widget from the host (you declare it in your template and call widget.openChat?. or programmatically focus the input). |
Focus trap in modals
The widget includes a generic focus trap (createFocusTrap) that activates when any dialog opens (voice search, image search, comparator, filter drawer, cart drawer). Behavior:
- On activation, focus moves to the first focusable element or the declared
initialFocus. - Cycles with
Tab/Shift+Tabinside the container. - On close, focus is restored to the element that opened it.
ARIA on key components
// SearchInput
<input
role="combobox"
aria-expanded={showSuggestions}
aria-controls="neuroon-suggestions"
aria-activedescendant={activeSuggestionId}
aria-autocomplete="list"
aria-label={t('search.inputAriaLabel')}
/>
<ul role="listbox" id="neuroon-suggestions" aria-label={t('suggestions.autocompleteAriaLabel')}>
<li role="option" aria-selected={isActive}>…</li>
</ul>
aria-label keys live in the i18n bundle and are translated to the active locale.
Reduced motion
The widget honors prefers-reduced-motion: reduce:
export function prefersReducedMotion(): boolean {
if (typeof window === 'undefined') return false
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}
Screen reader announcements
The widget emits role="status" / aria-live="polite" announcements when:
- Results finish loading (
Showing N of T products). - A filter is applied/removed (
Brand filter: Apple applied). - A product is added/removed from the comparator.
- The AI assistant finishes "thinking".
Keys live under filters.filtersAndSuggestions, comparison.panelExpanded, cart.a11y.itemAdded, etc..
Mobile inputs
All inputs use font-size >= 16px on mobile to avoid iOS auto-zoom. The widget also sets touch-action: manipulation and -webkit-text-size-adjust: 100% on root to normalize behavior across browsers.
Best practices for the host
- Avoid wrapping the widget inside a container with
overflow: hiddenthat clips modals — the widget renders modals into its ownportalContainerinside the Shadow Root, but if the host constrainsposition: fixedfrom an ancestor withtransform, the modal can be clipped. - If your theme forces
prefers-reduced-motion: reducevia its own toggle, propagate the media query: the widget reads it directly fromwindow.matchMedia. - Do not reorder shadow tree nodes from the host. The tree is controlled by Preact.
Further reading
- Widget events — events your host can react to.
- Theming — contrast tokens and ratios.
- Reference → CSS variables — typography and radii tokens.