RGAA Lab

Composants accessibles

Les champs de formulaire

Un champ sans étiquette liée est invisible pour qui ne voit pas le formulaire. Comment lier l'étiquette, l'aide et l'erreur, et annoncer ce qui a échoué. Démo, code, critères RGAA.

10 critères RGAA en jeu

Essayez-les

Pour recevoir le récapitulatif de vos audits — par exemple prenom@exemple.fr.

Fréquence d'envoi

Le champ dit qu’il est obligatoire avant qu’on l’envoie — dans l’étiquette, et par required. Soumettez le formulaire vide : l’erreur s’affiche en texte, le focus revient au champ fautif, et le message lui est lié — un lecteur d’écran le restitue donc avec l’étiquette. Tabulez ensuite jusqu’aux boutons radio : le groupe est un seul arrêt, et Espace choisit l’option qui a le focus. Puis remplissez : la confirmation est annoncée sans qu’on ait à la chercher.

Cette démo existe aussi en page autonome.

L’erreur courante

Le placeholder utilisé comme étiquette. C’est l’erreur la plus répandue du web, et elle coûte trois choses d’un coup.

<!-- À ne pas faire : le placeholder n'est pas une étiquette -->
<input type="email" placeholder="Adresse électronique" />
  • Il disparaît à la saisie. L’utilisateur qui s’interrompt ne sait plus ce qu’il remplissait — et celui qui relit son formulaire avant de l’envoyer ne peut plus vérifier quel champ est lequel.
  • Il n’est pas un nom accessible fiable. Certains lecteurs d’écran le lisent, d’autres non, d’autres encore le lisent après avoir dit « champ de saisie, vide ». Le RGAA ne l’accepte pas comme étiquette.
  • Son contraste est presque toujours insuffisant. Le gris clair par défaut échoue au critère 3.2, et le foncer le rend indistinguable d’une valeur déjà saisie.

Le placeholder n’est pas interdit : il est un exemple de format, à côté d’une vraie étiquette. « prenom@exemple.fr » est un bon placeholder ; « Adresse électronique » est une étiquette.

Le code, pas à pas

1. L’étiquette liée

<label for="courriel">Adresse électronique</label>
<input id="courriel" type="email" />

Le for de l’étiquette porte l’id du champ. Deux conséquences : le lecteur d’écran annonce « Adresse électronique, champ de saisie » quand le focus arrive, et cliquer sur l’étiquette place le focus dans le champ — une cible plus grande, qui aide tout le monde.

Envelopper le champ dans le <label> fonctionne aussi, mais liez explicitement malgré tout : c’est la forme que tous les lecteurs d’écran gèrent, et la seule qui survit à un déplacement du champ dans la mise en page.

2. L’aide, reliée elle aussi

Un texte d’aide placé sous l’étiquette est visible de tous — sauf de qui ne voit pas le formulaire, si rien ne le relie au champ.

<label for="courriel">Adresse électronique</label>
<p id="courriel-aide">Pour recevoir le récapitulatif de vos audits.</p>

<input id="courriel" type="email" aria-describedby="courriel-aide" />

aria-describedby ajoute la description après le nom : « Adresse électronique, champ de saisie, pour recevoir le récapitulatif de vos audits ». L’étiquette nomme, la description explique — ne mettez pas l’une à la place de l’autre.

3. Ce qui doit être su avant d’envoyer

Le critère 11.10 ne commence pas au message d’erreur. Ses trois premiers tests portent sur ce que le formulaire doit dire préalablement à la validation : quels champs sont obligatoires, et sous quel format on attend la donnée.

<label for="courriel">Adresse électronique (obligatoire)</label>
<p id="courriel-aide">
	Pour recevoir le récapitulatif de vos audits — par exemple prenom@exemple.fr.
</p>

<input id="courriel" type="email" required aria-describedby="courriel-aide" />

Le test 11.10.1 accepte l’un ou l’autre : une indication visible qui identifie nommément le champ, ou required / aria-required="true". Mais dès que l’attribut est là, le test 11.10.2 réclame en plus l’indication visible, dans l’étiquette ou dans le passage de texte associé au champ. Les deux, donc, en pratique : l’attribut pour la restitution, la mention écrite pour l’écran.

L’astérisque seule ne suffit pas si sa légende est ailleurs sur la page : le champ doit être identifiable nommément, et « Adresse électronique (obligatoire) » l’est sans renvoi. Le format attendu, lui, relève du test 11.10.5 — l’exemple placé dans l’aide le donne à tout le monde, avant l’erreur plutôt qu’après.

4. L’erreur : un texte, lié, et pas seulement une couleur

<input
	id="courriel"
	type="email"
	aria-describedby="courriel-aide courriel-erreur"
	aria-invalid="true"
/>

<p id="courriel-erreur">
	<span aria-hidden="true"></span>
	L'adresse doit contenir un « @ » — par exemple prenom@exemple.fr.
</p>

Trois points valent d’être notés. aria-describedby accepte plusieurs identifiants séparés par des espaces, et les descriptions sont concaténées dans l’ordre où les identifiants sont écrits — ici l’aide, puis l’erreur. Ce que le lecteur d’écran en restitue, et à quel moment, dépend de lui et de son niveau de verbosité : l’ordre est spécifié, la lecture ne l’est pas. aria-invalid dit l’état du champ, ce que la bordure rouge dit visuellement — la couleur ne va jamais seule. Et le symbole ⚠ est masqué : il double la couleur pour l’œil, il n’a rien à annoncer en plus du texte.

5. Annoncer, sans faire chercher

Le glossaire du référentiel définit le message de statut ainsi : « Un message de statut informe l’utilisateur d’un changement de contenu dans la page sans interrompre son activité principale (il n’y a pas de changement de contexte par exemple un repositionnement du focus sur le message). » Deux implémentations en découlent pour l’erreur, et il faut trancher entre elles :

  • Le focus va au premier champ fautif. C’est un changement de contexte : le message n’est pas un message de statut, et le critère 7.5 ne s’y applique pas. Ni role="alert" ni aria-live — le champ se réannonce avec son état et sa description, qui porte maintenant le message.
  • Le focus reste où il est. Rien ne conduit alors à l’erreur : le message avertit de son existence sans interrompre la saisie, et le test 7.5.2 demande role="alert".

Cumuler les deux fait annoncer la même chose deux fois. La démo prend le premier parti, comme le récapitulatif d’erreurs ; le second se justifie quand la vérification se fait au fil de la frappe, sans soumission.

Dans les deux cas, le message reste en place tant que l’erreur dure.

La confirmation, elle, n’appelle aucune action et ne prend jamais le focus : c’est un message de statut qui informe de la réussite, et le test 7.5.1 demande role="status". La région doit exister avant l’annonce, vide : créée au moment où le message arrive, elle n’est pas restituée par tous les lecteurs d’écran.

<!-- Présent dès le chargement, rempli plus tard -->
<p role="status"></p>

6. Les champs de même nature, groupés — si nécessaire

<fieldset>
	<legend>Fréquence d'envoi</legend>

	<label><input type="radio" name="frequence" value="hebdo" /> Chaque semaine</label>
	<label><input type="radio" name="frequence" value="mensuel" /> Chaque mois</label>
</fieldset>

Le critère 11.5 demande le regroupement « si nécessaire » : des champs sans rapport les uns avec les autres n’ont rien à réunir, et un fieldset posé autour de tout le formulaire n’apprend rien. Ce qui se regroupe, ce sont les champs de même nature — les options d’un même choix, les lignes d’une même adresse.

Le test 11.5.1 offre trois moyens équivalents : <fieldset>, une balise avec role="group", ou — pour des boutons radio — role="radiogroup". Le <fieldset> natif est le plus sûr : il ne dépend d’aucun script et les navigateurs lui donnent seuls son rôle.

Le regroupement ne dit encore rien de ce qu’il regroupe : c’est le critère 11.6 qui exige une légende, et le critère 11.7 qu’elle soit pertinente. Sans elle, un lecteur d’écran annonce « Chaque semaine, bouton radio » et l’utilisateur ne sait pas de quoi il choisit la fréquence. La legend est restituée avec les options du groupe — nommez la question posée, « Fréquence d’envoi », pas le gabarit qui l’a produite, « Choix 1 ».

7. autocomplete, presque gratuit

<input id="courriel" type="email" autocomplete="email" />

Une ligne, et le navigateur remplit à la place de l’utilisateur. Pour qui saisit difficilement, c’est la différence entre un formulaire long et un formulaire faisable. C’est aussi ce que demande le critère 11.13 pour tout champ qui porte une information sur l’utilisateur lui-même — nom, téléphone, adresse. La liste des valeurs est normalisée, et le test 11.13.1 vérifie les trois choses à la fois : l’attribut est présent, sa valeur appartient à la liste, et elle correspond à ce que le champ attend. autocomplete="name" sur un champ de téléphone échoue comme s’il n’y avait rien.

8. La même étiquette, d’un écran à l’autre

Un champ qui revient — le courriel, le code postal, la date de naissance — porte le même nom partout où il a la même fonction. Le critère 11.3 le demande à l’échelle de la page comme à l’échelle du site.

<!-- Formulaire d'inscription -->
<label for="courriel">Adresse électronique</label>

<!-- Formulaire de contact : même fonction, donc même étiquette -->
<label for="courriel">Adresse électronique</label>

Le défaut ne se voit pas sur un écran, il se voit sur trois. Chaque formulaire est écrit à part, souvent par des gens différents, et le champ devient « Courriel », puis « E-mail », puis « Votre adresse ». Qui ne voit pas la mise en page n’a que ces noms pour se repérer : trois noms, trois champs à réapprendre.

L’inverse coûte autant, et relève cette fois du critère 11.2 : une même étiquette posée sur des champs de fonctions différentes — trois « Date » dans le même formulaire, pour le départ, le retour et la naissance. C’est le défaut des formulaires générés, où le gabarit reprend le nom du type de donnée et non celui du rôle qu’elle joue. Nommez le rôle : « Date de départ », « Date de retour ».

Au clavier

ToucheEffet
TabPasse au champ suivant. Un groupe de boutons radio compte pour un seul arrêt.
Change l’option choisie à l’intérieur d’un groupe de boutons radio.
EspaceCoche une case à cocher ; choisit le bouton radio qui a le focus.
EntréeSoumet le formulaire depuis n’importe quel champ de saisie.

Ce que le lecteur d’écran restitue

Avec le code ci-dessus, à l’arrivée dans le champ : le nom, le type, le caractère obligatoire, puis la description — de l’ordre de « Adresse électronique (obligatoire), champ de saisie, requis, pour recevoir le récapitulatif de vos audits, par exemple prenom@exemple.fr ». Après une erreur, le champ se réannonce avec son état et le message : « Adresse électronique (obligatoire), champ de saisie, invalide, … l’adresse doit contenir un @ ». La formulation varie d’un lecteur à l’autre ; les informations, non.

Si vous n’entendez que « champ de saisie, vide », l’étiquette n’est pas liée — c’est le défaut du critère 11.1, et c’est celui qu’on relève le plus souvent en audit.

Checklist

  • Une <label for> par champ, avec un texte qui dit ce qu’on attend.
  • Le placeholder est un exemple de format, jamais l’étiquette.
  • Le caractère obligatoire et le format attendu sont dits avant la validation : required et la mention visible dans l’étiquette ou l’aide.
  • Aide et erreur reliées par aria-describedby (plusieurs identifiants possibles).
  • L’erreur est un texte qui dit comment corriger, doublée de aria-invalid — jamais la couleur seule.
  • Le focus va au premier champ fautif ; s’il ne s’y déplace pas, le message porte role="alert" — jamais les deux. role="status" pour la confirmation, sur une région présente dès le premier rendu.
  • Champs de même nature regroupés si nécessaire (fieldset ou role="group"), avec une légende qui nomme la question ; autocomplete sur ce que le navigateur sait remplir.
  • Une fonction, une étiquette : la même d’un formulaire à l’autre, et jamais deux fonctions sous le même nom.

Les critères RGAA en jeu

  • Critère 11.1 — Étiquette de champ de formulaire

    Chaque champ porte une étiquette, et cette étiquette lui est liée — par label for, ou à défaut par aria-label ou aria-labelledby. C'est le critère que ce composant met le plus souvent en défaut.

  • Critère 11.2 — Pertinence de l’étiquette

    L'étiquette doit dire ce qu'on attend dans le champ. « Champ 1 » est lié, mais ne renseigne personne : lié ne suffit pas, il faut pertinent.

  • Critère 11.3 — Cohérence des étiquettes répétées

    Le même champ change de nom d'un écran à l'autre — « Courriel » à l'inscription, « E-mail » au contact, « Votre adresse » ailleurs. Chaque formulaire est écrit séparément, et personne ne relit l'ensemble.

  • Critère 11.5 — Regroupement des champs de même nature

    Des champs de même nature — un groupe de boutons radio, une adresse en plusieurs champs — restent côte à côte sans que rien ne les réunisse ; le test 11.5.1 accepte pour cela un fieldset, un role="group" ou un role="radiogroup", et le critère ne l'exige que si le regroupement a un sens.

  • Critère 11.6 — Légende des regroupements de champs

    Le regroupement existe mais ne se nomme pas : le lecteur d'écran annonce « Chaque semaine, bouton radio » et rien ne dit de quoi on choisit la fréquence.

  • Critère 11.7 — Pertinence de la légende

    La légende reprend le nom technique du groupe — « Choix », « Options », « radio_1 » — au lieu de la question posée à l'utilisateur.

  • Critère 11.10 — Contrôle de saisie

    Le contrôle de saisie ne commence pas à la validation : le caractère obligatoire et le format attendu doivent être connus avant, et l'erreur ensuite signalée en texte à côté du champ fautif, jamais par la seule bordure rouge.

  • Critère 11.11 — Suggestions de correction de saisie

    Le message doit aider à corriger, pas seulement constater. « Adresse invalide » constate ; « l'adresse doit contenir un @ » corrige.

  • Critère 11.13 — Remplissage automatique des champs

    L'attribut autocomplete est la ligne qu'on oublie : sans elle, le navigateur ne remplit rien, et qui saisit difficilement retape tout — encore faut-il une valeur de la liste normalisée, et pertinente pour le champ.

  • Critère 7.5 — Messages de statut

    La confirmation d'envoi n'appelle aucune action et ne prend donc pas le focus : sans role="status", rien ne dit que l'inscription est passée. Le message d'erreur, lui, relève du test 7.5.2 et de son role="alert" tant qu'on ne déplace pas le focus vers l'erreur.

Vérifiez votre composant avec l'extension

Pour aller plus loin

Sources