Le sélecteur de date
La bonne question n'est pas comment coder un calendrier, mais s'il en faut un. Trois champs jour/mois/année suffisent presque toujours — et coûtent cent fois moins cher. Démo, code, critères RGAA.
6 critères RGAA en jeu
Essayez-le
Format attendu : jour/mois/année, en chiffres — par exemple 14/03/2026.
septembre 2026
| Lundi | Mardi | Mercredi | Jeudi | Vendredi | Samedi | Dimanche |
|---|---|---|---|---|---|---|
Tapez 14/03/2026 directement dans le champ : le calendrier n’a jamais eu à
s’ouvrir. Ouvrez-le ensuite au clavier — le focus entre sur un jour, les flèches se déplacent de jour en jour et changent de mois toutes seules aux
bords, Échap referme et rend le focus au bouton. Choisissez un jour :
le focus revient dans le champ, où la valeur vient d’être écrite.
Cette démo existe aussi en page autonome.
L’erreur courante
Le calendrier seul, avec un champ qu’on ne peut pas remplir.
<!-- À ne pas faire : la seule voie d'entrée est le calendrier -->
<input id="naissance" readonly onclick="ouvrirLeCalendrier()" /> C’est le motif le plus répandu des bibliothèques de composants, et il coûte cher à trois personnes différentes.
- Celui qui connaît sa date. Une date de naissance en 1954, c’est plus de huit cents pressions sur « mois précédent ». Il l’aurait tapée en huit frappes.
- Celui qui vise mal. Les cases d’un calendrier font trente pixels de côté et se touchent. Un tremblement, et c’est le 13 au lieu du 14 — sans que rien ne le signale.
- Celui qui ne voit pas la grille. Il doit reconstruire mentalement un tableau de six lignes pour trouver un jour dont il connaissait déjà le quantième.
Le readonly aggrave encore : le champ reste focalisable mais refuse la
saisie, si bien qu’on arrive dessus, qu’on tape, et qu’il ne se passe rien.
Trois variantes secondaires, du même esprit :
- Le format donné seulement dans le message d’erreur. L’utilisateur doit se tromper pour apprendre ce qu’on attendait. Le critère 11.10 demande l’indication du format préalablement à la validation du formulaire.
- Des
<div>cliquables comme jours. Le calendrier s’ouvre au clavier et s’y termine : plus rien n’est atteignable une fois dedans. - Le calendrier qui s’ouvre sans que le focus y aille, et qui se ferme sans le rendre. Le focus reste sur le corps de page ; la tabulation suivante repart du haut du document.
Le code, pas à pas
1. Avant tout : faut-il un calendrier ?
Une seule question tranche, et elle ne porte pas sur la technique : l’utilisateur connaît-il déjà la date ?
- Il la connaît — naissance, date d’une facture, jour d’un rendez-vous déjà pris. Il veut la saisir. Un calendrier ne fait que l’empêcher de taper.
- Il ne la connaît pas — choisir parmi des créneaux libres, comparer des tarifs selon le jour de la semaine. Il veut voir un mois. Le calendrier gagne son coût.
Dans le premier cas, qui est le plus courant, tout ce qui suit à partir du point 5 est du travail inutile.
2. Trois champs, dans un fieldset
<fieldset>
<legend>Date de naissance</legend>
<p id="naissance-format">Par exemple : 14 03 1954</p>
<label for="naissance-jour">Jour</label>
<input id="naissance-jour" type="text" inputmode="numeric" maxlength="2"
autocomplete="bday-day" aria-describedby="naissance-format" />
<label for="naissance-mois">Mois</label>
<input id="naissance-mois" type="text" inputmode="numeric" maxlength="2"
autocomplete="bday-month" />
<label for="naissance-annee">Année</label>
<input id="naissance-annee" type="text" inputmode="numeric" maxlength="4"
autocomplete="bday-year" />
</fieldset> Le fieldset n’est pas décoratif : le glossaire du référentiel donne
« trois champs successifs pour saisir une date (jour/mois/année) » comme
premier exemple de champs de même nature.
Sans lui, un lecteur d’écran annonce « Jour, champ de saisie » sans dire jour
de quoi.
Trois précisions qui font la différence à l’usage. Les étiquettes s’écrivent en toutes lettres — « Jour », pas « JJ » : si vous tenez au format abrégé
visible, le nom accessible doit contenir cet intitulé visible, c’est le test
11.2.5. inputmode="numeric" fait apparaître le pavé numérique sur mobile sans
les restrictions de type="number". Et autocomplete laisse le navigateur
remplir les trois champs d’un coup, ce qui est souvent la différence entre un
formulaire long et un formulaire faisable.
Ne déplacez pas le focus automatiquement d’un champ à l’autre quand il est plein : celui qui se corrige se retrouve à taper dans le champ suivant.
3. <input type="date">, ce qu’il donne et ce qu’il coûte
<label for="depart">Date de départ</label>
<input id="depart" type="date" /> Le navigateur fournit alors le clavier, le calendrier, la validation et la traduction — sans une ligne de JavaScript. C’est beaucoup pour un attribut.
Ses limites sont réelles, et il faut les connaître avant de choisir. La
présentation du champ ne se style presque pas. Le format affiché suit la langue
du système, pas celle de la page : un même formulaire montre jj/mm/aaaa ici et mm/dd/yyyy ailleurs. Et le calendrier déroulant appartient au navigateur — son
comportement au clavier varie de l’un à l’autre, vous ne le corrigerez pas.
Il reste le bon choix chaque fois que l’apparence n’est pas contractuelle.
4. Le format, donné avant la saisie
<label for="audit-date">Date de l'audit</label>
<p id="audit-date-format">
Format attendu : jour/mois/année, en chiffres — par exemple 14/03/2026.
</p>
<input id="audit-date" type="text" aria-describedby="audit-date-format" /> Le test 11.10.5 accepte deux conditions au choix, l’une comme l’autre
préalables à la validation : une instruction visible qui identifie nommément le
champ concerné, ou une instruction visible située dans l’étiquette ou dans le
passage de texte associé au champ. Ici l’indication ne nomme pas le champ ;
c’est aria-describedby qui satisfait la seconde, et qui fait restituer le
format en même temps que l’étiquette, à l’arrivée dans le champ. Le test
n’impose pas la liaison — il impose la visibilité avant la validation.
Quand la saisie échoue, le message répète le format au lieu de constater :
« la date s’écrit jour/mois/année, en chiffres — par exemple 14/03/2026 », et
non « date invalide ». Le champ porte alors aria-invalid="true", et le message
rejoint l’aria-describedby :
<input
id="audit-date"
aria-describedby="audit-date-format audit-date-erreur"
aria-invalid="true"
/> Acceptez aussi les formes voisines plutôt que de les refuser : 14/3/2026, 14-03-2026, 14 03 2026 disent tous la même chose. Normaliser coûte trois
lignes ; refuser coûte un abandon.
5. Si le calendrier est nécessaire : une grille
Un calendrier est un tableau de dates dans lequel on se déplace. C’est
exactement ce que décrit role="grid" — et une vraie <table> en fournit déjà
la structure.
<p id="calendrier-mois">mars 2026</p>
<table role="grid" aria-labelledby="calendrier-mois">
<tr>
<th scope="col"><span aria-hidden="true">L</span><span class="sr-only">Lundi</span></th>
…
</tr>
<tr>
<td role="gridcell" aria-selected="true">
<button type="button" tabindex="0" aria-label="samedi 14 mars 2026" aria-current="date">
14
</button>
</td>
…
</tr>
</table> role="grid" change la nature d’interaction du tableau, pas sa structure :
les <tr> restent des lignes et les <td> des cellules, il n’y a rien à
réécrire. L’aria-labelledby vers le mois affiché donne son nom à la grille.
Le nom accessible de chaque jour porte la date entière — « samedi 14 mars 2026 » — et contient le quantième visible, ce qu’exige le test 7.1.3. Sans lui, un lecteur d’écran annonce « 14, bouton » : quatorze de quel mois ?
aria-selected sur la cellule dit le jour choisi ; aria-current="date" dit le
jour d’aujourd’hui. Deux informations différentes, deux attributs différents —
et chacune doublée d’un repère visuel qui ne soit pas seulement une couleur.
Un seul jour est tabulable (tabindex="0"), tous les autres portent tabindex="-1" : entrer dans la grille coûte une tabulation, pas trente et une.
C’est le même tabindex roulant que dans les onglets, et
pour la même raison.
6. Le focus conduit, le mois annoncé
Quatre déplacements de focus, et ils sont tout le composant :
- À l’ouverture, le focus entre sur le jour choisi — ou sur aujourd’hui.
- Aux flèches, il suit le curseur, y compris quand on sort du mois : la grille se reconstruit autour du nouveau jour.
- À Échap, il revient sur le bouton qui a ouvert le calendrier.
- Au choix d’un jour, il revient dans le champ de saisie — c’est là que la valeur vient d’être écrite, et le lecteur d’écran la relit avec l’étiquette.
Reste un cas que rien n’annonce : les boutons « mois précédent » et « mois suivant ». Le focus y demeure, leur nom ne change pas, la grille se refait en silence. Il faut donc une région de statut, présente dès le premier rendu et vide :
<!-- Présente au chargement, remplie au changement de mois -->
<p role="status" class="sr-only"></p> Aux flèches, en revanche, n’annoncez rien : le nom de la cellule atteinte porte déjà « 1 avril 2026 », et l’annonce ferait doublon.
Au clavier
| Touche | Effet |
|---|---|
| Tab | Passe du champ au bouton du calendrier, puis au reste du formulaire. La grille ouverte ne compte que pour un arrêt. |
| ← → | Jour précédent, jour suivant. Aux bords du mois, la grille change de mois et le focus suit. |
| ↑ ↓ | Semaine précédente, semaine suivante. |
| Début / Fin | Premier ou dernier jour de la semaine où se trouve le focus. |
| Page préc. / Page suiv. | Mois précédent ou suivant, le focus restant sur le même quantième. |
| Entrée / Espace | Choisit le jour, referme la grille, et pose le focus dans le champ. |
| Échap | Referme la grille et rend le focus au bouton qui l’a ouverte. |
Ce que le lecteur d’écran restitue
À l’arrivée dans le champ, l’étiquette puis le format — de l’ordre de « Date de l’audit, champ de saisie, format attendu : jour/mois/année, en chiffres, par exemple 14/03/2026 ». Sur le bouton : « Choisir dans le calendrier, bouton, réduit ».
Dans la grille : le nom du mois, la nature du composant, puis la date complète — « mars 2026, grille, samedi 14 mars 2026, sélectionné ». La formulation et la position annoncée varient d’un lecteur d’écran à l’autre ; les informations, non.
Trois diagnostics inverses. Si vous n’entendez que « 14, bouton », le nom
accessible de la cellule se limite au quantième. Si vous n’entendez pas le mois
en entrant dans la grille, l’aria-labelledby manque. Et si rien ne se dit
quand vous activez « mois suivant », c’est qu’il n’y a pas de région de statut —
le mois a changé pour l’œil seulement.
Checklist
- Demandez-vous d’abord si l’utilisateur connaît déjà la date : s’il la connaît, il veut la taper.
- Trois champs de même nature dans un
fieldsetaveclegend, chacun avec sa propre<label for>. - Le format attendu visible avant la saisie, relié par
aria-describedby— et répété dans l’erreur. - La saisie textuelle reste possible même quand un calendrier existe ; jamais de champ
readonly. role="grid"sur une vraie<table>, nommée par le mois affiché, avec un seul jour tabulable.- Nom accessible de chaque jour = la date complète ;
aria-selectedpour le choix,aria-current="date"pour aujourd’hui. - Focus conduit aux quatre moments : ouverture, flèches, Échap, choix.
- Région
role="status"présente dès le départ, pour le changement de mois par les boutons.
Les critères RGAA en jeu
- Critère 11.1 — Étiquette de champ de formulaire
Chaque champ de la date porte sa propre étiquette liée : trois champs jour/mois/année, c'est trois étiquettes, et non une seule posée sur le groupe.
- Critère 11.2 — Pertinence de l’étiquette
Les abréviations JJ, MM, AAAA sont l'intitulé visible, et le nom accessible doit le contenir : un aria-label Jour sur un champ marqué JJ contredit le test 11.2.5.
- Critère 11.5 — Regroupement des champs de même nature
Le glossaire du référentiel cite exactement ce cas — trois champs successifs pour saisir une date — comme des champs de même nature à regrouper dans un fieldset.
- Critère 11.10 — Contrôle de saisie
Le format attendu doit être visible avant la validation, pas révélé par le message d'erreur : c'est ce que demande le test 11.10.5, et c'est le défaut le plus fréquent des dates.
- Critère 7.1 — Compatibilité des scripts
Un calendrier est un composant entièrement fabriqué : la grille, le jour choisi, le jour du jour et le changement de mois n'existent pour les technologies d'assistance que si on les déclare.
- Critère 7.3 — Contrôle des scripts au clavier
La grille se parcourt aux flèches, et le script ne doit pas perdre le focus en chemin — ni à l'ouverture du calendrier, ni à sa fermeture, ni au changement de mois.
Vérifiez votre composant avec l'extension
Pour aller plus loin
- Les champs de formulaire : l’étiquette, l’aide et l’erreur, dont ce composant n’est qu’un cas particulier coûteux.
- Critère 11.10 — Contrôle de saisie : ce qu’un audit vérifie sur le format annoncé, test par test.
- Critère 11.5 — Regroupement des champs de même nature et « champs de même nature » au glossaire : la définition qui cite la date en trois champs.
- Les onglets : le même tabindex roulant, sur un composant plus simple à observer.
Sources
- ARIA APG — Grid Pattern — le contrat de comportement d’une grille parcourue aux flèches
- MDN —
<input type="date">— ce que le natif fournit, et ses limites - WCAG — Understanding 3.3.2 Labels or Instructions — pourquoi le format se donne avant la saisie
- GOV.UK Design System — Date input — les trois champs, éprouvés à grande échelle