Saltar al contenido principal

Accesibilidad

El widget se ha construido para cumplir WCAG 2.2 AA. Esta página enumera lo que el widget hace por ti y lo que tu integración debe seguir respetando.

Cumplimiento por criterio

Criterio WCAGImplementación
1.4.3 Contraste mínimoTokens light/dark con contraste ≥ 4.5:1 en texto y 3:1 en componentes UI.
1.4.10 ReflowLayout responsive sin scroll horizontal a 320 px.
1.4.13 Content on hover/focusTooltips dismissibles con Esc y persistentes hasta blur.
2.1.1 KeyboardToda la UI es accesible por teclado. Sin trampas.
2.1.2 No keyboard trapLos modales atrapan foco con escape vía Esc y restauración.
2.4.3 Focus orderOrden lógico: input → toolbar (voz/imagen) → resultados → filtros.
2.4.7 Focus visibleAnillos de foco con --nrn-primary a 4 px.
3.2.2 On inputEl submit no se dispara hasta que el usuario presiona Enter o el botón.
3.3.2 LabelsTodos los inputs tienen aria-label o <label> asociado.
4.1.2 Name, role, valuecombobox, listbox, option, dialog, status correctamente aplicados.
4.1.3 Status messagesCambios de estado anunciados con role="status" (resultados cargados, filtros aplicados).

Shadow DOM y herramientas asistivas

El widget se monta en un Shadow DOM en modo open. Los lectores de pantalla modernos (NVDA, JAWS, VoiceOver, TalkBack) navegan correctamente entre los nodos del Shadow Tree y el documento del host.

TeclaAcción
Tab / Shift+TabMover foco entre elementos del widget.
EnterLanzar la búsqueda con la query del input. Activa botones y enlaces.
EscCerrar modal/drawer/dropdown abierto. Si no hay nada abierto, limpia la query.
/ Navegar sugerencias y resultados.
/ Navegar carouseles (top products, comparación, kit).
Cmd+K / Ctrl+KAtajo opcional para abrir el widget desde el host (lo declaras tú en tu plantilla y haces widget.openChat?. o foco programático sobre el input).

Focus trap en modales

El widget incluye un focus trap genérico (createFocusTrap) que se activa al abrir cualquier dialog (búsqueda por voz, búsqueda por imagen, comparador, drawer de filtros, drawer de carrito). Comportamiento:

  • Al activarse, mueve el foco al primer elemento focuseable o al initialFocus declarado.
  • Cicla con Tab / Shift+Tab dentro del contenedor.
  • Al cerrarse, restaura el foco al elemento que lo abrió.

ARIA en componentes clave

// 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>

Las claves de aria-label viven en el bundle de i18n y se traducen al locale activo.

Reduced motion

El widget respeta prefers-reduced-motion: reduce:

export function prefersReducedMotion(): boolean {
if (typeof window === 'undefined') return false
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}

Anuncios para screen reader

El widget emite anuncios role="status" / aria-live="polite" cuando:

  • Los resultados terminan de cargar (Mostrando N de T productos).
  • Se aplica/quita un filtro (Filtro Marca: Apple aplicado).
  • Se añade/quita un producto del comparador.
  • El asistente IA termina de "pensar".

Las claves están bajo filters.filtersAndSuggestions, comparison.panelExpanded, cart.a11y.itemAdded, etc..

Inputs en mobile

Todos los inputs tienen font-size >= 16px en mobile para evitar el auto-zoom de iOS. El widget también añade touch-action: manipulation y -webkit-text-size-adjust: 100% en el root para uniformizar el comportamiento entre navegadores.

Buenas prácticas para el host

  • Evita encerrar el widget dentro de un contenedor con overflow: hidden que recorte modales — el widget renderiza modales en su propio portalContainer dentro del Shadow Root, pero si el host limita el position: fixed desde un ancestro con transform, el modal puede recortarse.
  • Si tu tema fuerza prefers-reduced-motion: reduce con un toggle propio, propaga la media query: el widget la lee de window.matchMedia directamente.
  • No reordenes nodos del shadow tree desde el host. El árbol está controlado por Preact.

Próximas lecturas