Volver al inicio

Accesible DatePicker React WCAG 2.2

El artículo describe la creación de DatePicker WCAG 2.2 AA en React con correcciones de generación IA. Cubre desviaciones de APG, gestión de foco, aria-live assertive. Para desarrolladores senior.

DatePicker WCAG en React: de IA a ideal
Advertisement 728x90

Creando un DatePicker accesible en React conforme a WCAG 2.2 AA

Nuestro equipo de desarrollo comenzó con código de DatePicker generado por Claude basado en el patrón "Date Picker Dialog" de WAI-ARIA APG. El prompt inicial especificaba requisitos de WCAG 2.1/2.2 Nivel AA: input con aria-describedby, botón de activación, diálogo modal con role="dialog" y una tabla de calendario basada en grid. Sin embargo, la versión cruda reveló problemas críticos con el foco, la navegación y la accesibilidad.

La IA implementó navegación básica por teclado (flechas, Home/End, PageUp/Down, Enter/Espacio, Escape), tabindex móvil en elementos gridcell y aria-live="polite" para cambios de mes. Pero las pruebas con VoiceOver y NVDA expusieron problemas: el foco no caía en la fecha seleccionada al abrir, los anuncios aria-live se acumulaban en cola y el diálogo parpadeaba con clics repetidos.

Desviaciones del APG para una accesibilidad real

La especificación APG pide <td role="gridcell"> sin botones anidados, confiando en onClick/onKeyDown manual y tabindex. Las pruebas en el mundo real mostraron claras ventajas de usar <button> dentro de <td>:

Google AdInline article slot
  • Manejo nativo del foco y soporte para Enter/Espacio sin código extra.
  • Semántica automática para lectores de pantalla.
  • Estado deshabilitado integrado sin aria-disabled ni preventDefault.
  • Eventos de clic fiables desde ratón o teclado.

Esta fue una elección deliberada: el APG es una guía, pero las pruebas en dispositivos tienen prioridad. Aseguramos contraste ≥4.5:1, usamos CSS Modules y APIs nativas Date/Intl para formateo—sin dependencias externas más allá de React/TypeScript.

Solucionando problemas funcionales y de UX

El código crudo no compilaba: faltaba index.html y errores de TypeScript. Tras las correcciones, surgieron más issues:

Bugs funcionales:

Google AdInline article slot
  • Fechas fuera de minDate/maxDate saltaban a otro mes en vez de mostrar celdas deshabilitadas.
  • Sin forma de borrar el input.

Problemas visuales:

  • Estilos de foco faltantes en celdas.
  • Tamaños desiguales de td.

Accesibilidad:

  • Sin foco en fecha seleccionada al abrir—lectores de pantalla callados.
  • aria-live="polite" en encabezado de mes causaba colas de anuncios en navegación rápida.
  • Blur + clic en trigger provocaba parpadeo del diálogo.

Soluciones:

Google AdInline article slot
  • Al abrir, el foco va al valor o hoy; el lector de pantalla anuncia contexto (día de semana, fecha, mes, año).
  • aria-live="assertive" en región oculta separada para anuncios instantáneos de mes.
  • Tras selección, foco regresa al trigger con aria-label actualizado.
  • Mostrar mes del valor incluso con días deshabilitados.
  • Eliminados OK/Cancel: Enter confirma, Esc cierra.

Estructura del componente y navegación por teclado

Props: value (Date | null), onChange, minDate?, maxDate?, disabledDates?, locale?.

Claves ARIA:

  • Input: aria-describedby para formato, readonly.
  • Trigger: aria-label dinámico ("Seleccionado: DD/MM/AAAA" o "Elige una fecha").
  • Diálogo: role="dialog", aria-modal="true", trampa de foco.
  • Grid: table role="grid", td con button role="gridcell", aria-selected en valor, aria-disabled en fechas no disponibles.
  • Encabezado de mes: aria-live="assertive" en región educada.

Navegación:

  • Flechas: días adyacentes.
  • Home/End: primer/último día del mes.
  • PageUp/Down: ±1 mes.
  • Shift+PageUp/Down: ±1 año.
  • Enter/Espacio: seleccionar.
  • Escape: cerrar.

Lecciones clave

  • La IA acelera el esqueleto (ARIA, navegación), pero las pruebas con lectores de pantalla son esenciales.
  • <button> dentro de <td> es más fiable que gridcell puro en navegadores reales + lectores de pantalla.
  • aria-live="assertive" evita colas en navegación de meses.
  • Gestión de foco: abrir en valor/hoy, regresar a trigger.
  • Stack mínimo: React + TS, sin librerías.

El componente está listo para producción como selector de fecha única, con código abierto para extensiones (años, rangos).

— Editorial Team

Advertisement 728x90

Leer después