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> :
- 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 :
- 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 :
- À 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
Aucun commentaire pour le moment.