Skip to Content

Chapitre 9.3 — Erreurs & validation

⏱️ TL;DR — La gestion des erreurs est le point le plus difficile d’un formulaire accessible, et le plus souvent bâclé. Une bordure rouge ne dit rien à qui ne voit pas la couleur. Il faut : identifier l’erreur en texte (3.3.1), suggérer une correction (3.3.3), lier le message au champ avec aria-describedby, marquer le champ avec aria-invalid="true", ne pas se reposer sur la couleur seule, et surtout annoncer l’erreur — via un résumé d’erreurs en haut qui reçoit le focus, ou via une live region. Et on ne valide pas agressivement à chaque frappe.

🎯 Objectifs

  • Identifier chaque erreur en texte et proposer une correction.
  • Lier le message d’erreur à son champ (aria-describedby) et marquer le champ (aria-invalid).
  • Ne jamais transmettre l’erreur par la couleur seule.
  • Annoncer les erreurs : résumé d’erreurs focalisable ou live region.
  • Choisir le bon moment de validation, sans harceler l’utilisateur à la frappe.

Le réflexe à casser : « rouge = erreur »

Le pattern par défaut de beaucoup d’équipes : au submit, on colore en rouge la bordure des champs fautifs. Pour un utilisateur voyant sans trouble de la couleur, c’est (à peu près) lisible. Pour tous les autres, c’est le vide : rien n’est annoncé, rien n’est écrit, et la seule information (« ce champ est faux ») passe par une teinte que beaucoup ne perçoivent pas.

Une erreur accessible se construit sur cinq exigences :

  1. Un message en texte qui dit ce qui ne va pas (3.3.1).
  2. Une suggestion de correction quand elle est connue (3.3.3).
  3. Le message lié au champ via aria-describedby, et le champ marqué aria-invalid="true".
  4. Pas de couleur seule : ajoute une icône, un mot, un contour épais — un signal non chromatique (1.4.1).
  5. L’erreur annoncée à l’utilisateur au moment où elle apparaît.

📚 Aller plus loin — Les critères clés : 3.3.1 Identification des erreurs (niveau A, l’erreur est décrite en texte), 3.3.3 Suggestion après une erreur (niveau AA, on propose une correction si on la connaît), 1.4.1 Utilisation de la couleur (niveau A) et 4.1.2 Nom, rôle et valeur (l’état d’erreur exposé via aria-invalid).

Un champ en erreur, accessible

Voici le motif complet pour un champ e-mail invalide. Le message est en texte, lié au champ, le champ est marqué invalide, et le signal ne repose pas que sur le rouge.

<label for="email">Adresse e-mail (obligatoire)</label> <input type="email" id="email" name="email" autocomplete="email" required aria-invalid="true" aria-describedby="email-erreur" /> <p id="email-erreur" class="erreur"> <span aria-hidden="true">⚠️ </span> Entre une adresse e-mail valide, par exemple prenom@exemple.fr. </p>

Points à noter :

  • aria-invalid="true" : le lecteur d’écran annonce le champ comme « non valide ». On le retire (ou le passe à false) une fois l’erreur corrigée.
  • aria-describedby="email-erreur" : le message est annoncé après le nom du champ quand le focus y arrive.
  • Le message dit quoi corriger et donne un exemple — il coche à la fois 3.3.1 et 3.3.3.
  • L’icône ⚠️ est en aria-hidden="true" pour ne pas être lue deux fois ; le texte porte l’information, la couleur n’est qu’un renfort.

En React, c’est le même principe, avec le describedby conditionné à la présence d’erreur :

function ChampEmail({ valeur, onChange, erreur }) { return ( <div> <label htmlFor="email">Adresse e-mail (obligatoire)</label> <input type="email" id="email" name="email" autoComplete="email" value={valeur} onChange={onChange} required aria-invalid={erreur ? "true" : undefined} aria-describedby={erreur ? "email-erreur" : undefined} /> {erreur && ( <p id="email-erreur" className="erreur"> <span aria-hidden="true">⚠️ </span> {erreur} </p> )} </div> ); }

Côté utilisateur — Avec ce balisage, un utilisateur qui revient sur le champ entend « Adresse e-mail, obligatoire, non valide, zone de saisie, Entre une adresse e-mail valide, par exemple prenom@exemple.fr ». Il sait est le problème, ce qui ne va pas et comment le corriger — sans jamais avoir vu la moindre couleur. Compare avec la bordure rouge seule, où il n’entend rien du tout et croit son formulaire envoyé.

Annoncer les erreurs après un submit

Lier le message au champ ne suffit pas : encore faut-il que l’utilisateur découvre qu’il y a des erreurs. Au moment du submit, deux stratégies, souvent combinées :

Stratégie A — Le résumé d’erreurs focalisable (recommandé)

Affiche en haut du formulaire un bloc « Il y a N erreurs », avec un lien vers chaque champ fautif, puis déplace le focus sur ce bloc. L’utilisateur au clavier et au lecteur d’écran est amené directement à la liste des problèmes, et chaque lien l’emmène au champ concerné.

<div id="resume-erreurs" role="alert" tabindex="-1"> <h2>Il y a 2 erreurs dans le formulaire</h2> <ul> <li><a href="#email">Adresse e-mail : format invalide</a></li> <li><a href="#pwd">Mot de passe : 12 caractères minimum requis</a></li> </ul> </div>

Après le rendu, on donne le focus au conteneur (document.getElementById("resume-erreurs").focus()). Le tabindex="-1" le rend focalisable par script sans l’insérer dans l’ordre de tabulation. Le role="alert" le fait annoncer dès son apparition.

Stratégie B — La live region

Une zone aria-live (ou role="alert" / role="status") annonce un message dès qu’il change, sans déplacer le focus. Utile pour une erreur ponctuelle ou un statut (« Formulaire envoyé »). On détaille le fonctionnement des live regions — polite vs assertive, les pièges de timing — dans la partie ARIA.

<!-- Vide au départ ; on y injecte le message d'erreur après validation --> <div role="alert" aria-live="assertive" class="statut-formulaire"></div>

Pour un formulaire avec plusieurs champs fautifs, le résumé focalisable est en général plus efficace : il donne une vue d’ensemble et un moyen d’atteindre chaque champ. La live region brille pour les annonces courtes et uniques.

🧭 Sur A11yLearn — À l’inscription d’A11yLearn, valider un e-mail mal formé colorait le champ en rouge… et c’est tout. Un testeur au lecteur d’écran cliquait « S’inscrire », n’entendait rien, et pensait son compte créé — jusqu’à l’absence d’e-mail de confirmation. On a ajouté un résumé d’erreurs en haut, avec role="alert", tabindex="-1" et le focus déplacé dessus au submit ; chaque champ fautif a reçu son message lié en aria-describedby et son aria-invalid. Le testeur est désormais conduit directement à « Il y a 1 erreur : Adresse e-mail, format invalide », puis d’un coup d’Entrée jusqu’au champ. Plus aucun envoi « fantôme ».

Ne pas valider trop agressivement

Valider à chaque frappe est hostile : l’utilisateur tape la première lettre de son e-mail et s’entend déjà dire « adresse invalide ». Pour quelqu’un au lecteur d’écran, une erreur qui apparaît et disparaît à chaque caractère via une live region, c’est un flot d’annonces ininterrompu.

Quelques repères :

  • Valide plutôt au blur (quand l’utilisateur quitte le champ) et/ou au submit, pas à chaque keydown.
  • Une fois un champ passé en erreur, tu peux revalider à la frappe pour lever l’erreur dès qu’elle est corrigée — mais évite d’en créer de nouvelles en cours de saisie.
  • Ne bloque pas le submit sur une validation onChange fragile ; laisse l’utilisateur finir de penser.

⚠️ Piège — La bordure rouge sans message texte. C’est le grand classique : le champ devient rouge, aucun texte n’apparaît, rien n’est annoncé. Pour un daltonien, pour un utilisateur de lecteur d’écran, pour quelqu’un en plein soleil sur mobile, l’erreur est invisible. Règle simple : pas d’état d’erreur sans texte associé. La couleur illustre le message, elle ne le remplace jamais.

✏️ Exercices

Exercice 1 — Rends cette erreur accessible. Ce champ ne signale l’erreur que par une classe CSS qui met la bordure en rouge. Corrige le balisage.

<label for="cp">Code postal</label> <input type="text" id="cp" name="cp" class="champ-invalide" />

✅ Solution

Ajoute un message texte identifiant l’erreur et suggérant la correction, lie-le avec aria-describedby, et marque le champ aria-invalid :

<label for="cp">Code postal</label> <input type="text" id="cp" name="cp" inputmode="numeric" class="champ-invalide" aria-invalid="true" aria-describedby="cp-erreur" /> <p id="cp-erreur" class="erreur"> <span aria-hidden="true">⚠️ </span> Le code postal doit comporter 5 chiffres, par exemple 75001. </p>

La couleur (champ-invalide) reste, mais comme renfort : l’information vit désormais dans le texte, lié et annoncé.

Exercice 2 — Le formulaire est soumis avec 3 erreurs. Décris la stratégie d’annonce que tu mets en place et les attributs clés du bloc de résumé.

✅ Solution

Un résumé d’erreurs focalisable en haut du formulaire : un conteneur avec role="alert" (annoncé à l’apparition) et tabindex="-1" (focalisable par script). Au submit, on affiche la liste des 3 erreurs — chacune un lien href="#id-du-champ" avec un libellé clair — puis on déplace le focus sur ce conteneur. L’utilisateur au clavier et au lecteur d’écran est ainsi conduit directement à la liste et peut sauter à chaque champ fautif. En complément, chaque champ porte aria-invalid="true" et son message lié via aria-describedby.

🧠 Quiz de révision

1. Quelles sont les cinq exigences d’une erreur de formulaire accessible ?

(1) Un message en texte décrivant l’erreur (3.3.1), (2) une suggestion de correction si connue (3.3.3), (3) le message lié au champ via aria-describedby + le champ marqué aria-invalid="true", (4) pas de couleur seule (1.4.1), (5) l’erreur annoncée (résumé focalisable ou live region).

2. Que fait aria-invalid="true" et quand le retire-t-on ?

Il expose l’état « non valide » du champ aux technologies d’assistance, qui l’annoncent. On le retire (ou on le passe à false) dès que l’erreur est corrigée, pour ne pas laisser un champ valide marqué comme fautif.

3. Pourquoi le résumé d’erreurs a-t-il tabindex="-1" et reçoit-il le focus ?

tabindex="-1" le rend focalisable par script sans l’ajouter à l’ordre de tabulation naturel. En lui déplaçant le focus au submit, on conduit l’utilisateur clavier et lecteur d’écran directement à la liste des erreurs, au lieu de le laisser chercher ce qui a changé.

4. Pourquoi éviter de valider à chaque frappe ?

Parce que c’est hostile : l’utilisateur est déclaré en erreur avant d’avoir fini de saisir, et pour un utilisateur de lecteur d’écran une live region qui se met à jour à chaque caractère produit un flot d’annonces insupportable. On valide plutôt au blur et/ou au submit, quitte à lever l’erreur à la frappe une fois qu’elle est corrigée.

5. Une bordure rouge sans texte : qui laisse-t-on de côté ?

Les personnes daltoniennes (qui ne perçoivent pas le rouge), les utilisateurs de lecteur d’écran (rien n’est annoncé), et toute personne dans un contexte où la couleur passe mal (plein soleil, faible contraste). L’erreur leur est invisible. D’où la règle : pas d’état d’erreur sans texte associé.


Chapitre suivant : Autocomplete & saisie — faciliter la saisie et alléger l’effort demandé à l’utilisateur.

Last updated on