Retour à l'accueil

Accessible DatePicker React WCAG 2.2

L’article décrit la création d’un DatePicker WCAG 2.2 AA sur React avec corrections de génération IA. Couvre les écarts par rapport à l’APG, gestion du focus, aria-live assertive. Pour développeurs seniors.

DatePicker WCAG sur React : de l’IA à l’idéal
Advertisement 728x90

Créer un DatePicker React accessible conforme WCAG 2.2 AA

Notre équipe de développement a démarré avec du code de DatePicker généré par Claude, basé sur le pattern "Date Picker Dialog" de WAI-ARIA APG. La consigne initiale spécifiait les exigences WCAG 2.1/2.2 niveau AA : champ de saisie avec aria-describedby, bouton déclencheur, boîte de dialogue modale avec role="dialog", et un calendrier sous forme de grille tabulaire. Cependant, la version brute révélait des problèmes critiques de focus, de navigation et d'accessibilité.

L'IA avait implémenté une navigation clavier basique (flèches, Début/Fin, PageHaut/PageBas, Entrée/Espace, Échap), un tabindex itinérant sur les éléments gridcell, et aria-live="polite" pour les changements de mois. Mais les tests avec VoiceOver et NVDA ont mis en lumière des dysfonctionnements : le focus ne se posait pas sur la date sélectionnée à l'ouverture, les annonces aria-live s'accumulaient, et la boîte de dialogue clignotait lors de clics répétés.

Déviations de l'APG pour une accessibilité réelle

La spécification APG préconise des <td role="gridcell"> sans boutons imbriqués, en s'appuyant sur des onClick/onKeyDown manuels et tabindex. Les tests en conditions réelles ont montré des avantages nets à utiliser des <button> à l'intérieur des <td> :

Google AdInline article slot
  • Gestion native du focus et support Entrée/Espace sans code supplémentaire.
  • Sémantique automatique pour les lecteurs d'écran.
  • État désactivé intégré sans aria-disabled ni preventDefault.
  • Événements de clic fiables depuis la souris ou le clavier.

C'était un choix délibéré : l'APG est un guide, mais les tests sur appareils priment. Nous avons veillé à un contraste ≥4,5:1, utilisé des CSS Modules, et les API natives Date/Intl pour le formatage — sans dépendances externes hormis React/TypeScript.

Correction des bugs fonctionnels et UX

Le code brut ne compilait pas : index.html manquant et erreurs TypeScript. Après corrections, d'autres problèmes ont émergé :

Bugs fonctionnels :

Google AdInline article slot
  • Les dates hors minDate/maxDate sautaient à un autre mois au lieu d'afficher des cellules désactivées.
  • Aucune façon de vider le champ.

Problèmes visuels :

  • Styles de focus manquants sur les cellules.
  • Tailles de td inégales.

Accessibilité :

  • Pas de focus sur la date sélectionnée à l'ouverture — les lecteurs d'écran restaient muets.
  • aria-live="polite" sur l'en-tête de mois causait des files d'annonces en navigation rapide.
  • Flou + clic sur le déclencheur provoquait un clignotement de la boîte.

Corrections :

Google AdInline article slot
  • À l'ouverture, focus sur la valeur ou aujourd'hui ; le lecteur d'écran annonce le contexte (jour de la semaine, date, mois, année).
  • aria-live="assertive" dans une région cachée séparée pour des annonces de mois instantanées.
  • Après sélection, focus retourne au déclencheur avec aria-label mis à jour.
  • Affichage du mois de la valeur même avec des jours désactivés.
  • Suppression OK/Annuler : Entrée valide, Échap ferme.

Structure du composant et navigation clavier

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

Clés ARIA :

  • Champ : aria-describedby pour le format, readonly.
  • Déclencheur : aria-label dynamique ("Sélectionné : JJ/MM/AAAA" ou "Choisir une date").
  • Boîte de dialogue : role="dialog", aria-modal="true", piège de focus.
  • Grille : table role="grid", td avec button role="gridcell", aria-selected sur la valeur, aria-disabled sur les dates indisponibles.
  • En-tête de mois : aria-live="assertive" dans une région polite.

Navigation :

  • Flèches : jours adjacents.
  • Début/Fin : premier/dernier jour du mois.
  • PageHaut/PageBas : ±1 mois.
  • Maj+PageHaut/PageBas : ±1 an.
  • Entrée/Espace : sélection.
  • Échap : fermeture.

Enseignements clés

  • L'IA accélère le squelette (ARIA, navigation), mais les tests avec lecteurs d'écran sont indispensables.
  • <button> dans <td> est plus fiable que gridcell pur dans les vrais navigateurs + lecteurs d'écran.
  • aria-live="assertive" évite les files d'attente en navigation mensuelle.
  • Gestion du focus : ouverture sur valeur/aujourd'hui, retour au déclencheur.
  • Stack minimal : React + TS, sans bibliothèques.

Le composant est prêt pour la production en tant que sélecteur de date unique, avec code ouvert pour extensions (années, plages).

— Editorial Team

Advertisement 728x90

Lire ensuite