Courant Mis à jour : 2026-05

Validation et messages d'erreur

Comment signaler les erreurs de saisie de façon accessible : aria-invalid, aria-describedby, role=alert, déplacement du focus, et l'ordre des opérations à la soumission.

Table des matières

Un message d’erreur affiché visuellement mais non annoncé par les technologies d’assistance (AT) est inexistant pour l’utilisateur de lecteur d’écran. Il soumet le formulaire, rien ne se passe apparemment, il recommence sans jamais savoir ce qui ne va pas. C’est le scénario le plus fréquent sur les formulaires non configurés pour l’accessibilité.

Ce qui doit se passer à la soumission

Quand un formulaire est soumis avec des erreurs, trois choses doivent se produire dans l’ordre :

  1. Les champs en erreur sont marqués via aria-invalid="true" et un message d’erreur associé
  2. Les messages d’erreur sont annoncés via role="alert" ou une live region
  3. Le focus est déplacé sur le premier champ en erreur, ou sur un récapitulatif des erreurs

Aucune de ces trois étapes n’est optionnelle. L’absence de l’une d’entre elles crée une expérience incomplète pour les utilisateurs AT.

Marquer un champ comme invalide : aria-invalid

aria-invalid signale aux AT qu’un champ contient une valeur incorrecte. Il doit être mis à jour dynamiquement par JavaScript après la validation.

<!-- État initial — champ valide (aria-invalid absent ou false) -->
<input type="email" id="email" name="email">

<!-- Après soumission avec erreur -->
<input type="email" id="email" name="email"
       aria-invalid="true"
       aria-describedby="erreur-email">
→ NVDA au focus sur le champ en erreur :
  "Email, zone de saisie, invalide, Format attendu : nom@exemple.fr"

Valeurs possibles :

ValeurUsage
"true"Erreur générique — valeur incorrecte
"false"Valeur correcte (comportement par défaut)
"grammar"Erreur grammaticale dans un champ texte libre
"spelling"Erreur orthographique

Remettre à "false" après correction ou supprimer l’attribut. Un champ corrigé doit cesser d’être annoncé comme invalide.

Associer le message d’erreur : aria-describedby

aria-describedby lie le message d’erreur au champ. L’AT lit ce message après le nom et le rôle du champ, au focus.

<label for="cp">Code postal</label>
<input
  type="text"
  id="cp"
  aria-invalid="true"
  aria-describedby="erreur-cp aide-cp"
>
<p id="erreur-cp" class="erreur" role="alert">
  Le code postal doit contenir exactement 5 chiffres.
</p>
<p id="aide-cp" class="aide">
  Format attendu : 5 chiffres (ex. 29770)
</p>
→ NVDA au focus : "Code postal, zone de saisie, invalide,
  Le code postal doit contenir exactement 5 chiffres.
  Format attendu : 5 chiffres (ex. 29770)"

aria-describedby accepte plusieurs IDs séparés par des espaces, ils sont lus dans l’ordre. Mettre l’aide de format en premier, le message d’erreur en second.

Le message d’erreur doit être visible, pas seulement annoncé aux AT. Un message dans un aria-describedby qui pointe vers un élément hidden n’est pas lu par NVDA.

Annoncer l’erreur immédiatement : role="alert"

role="alert" transforme un élément en live region assertive : son contenu est annoncé dès qu’il change, en interrompant la lecture en cours.

<!-- Zone d'erreur dans le HTML, vide au chargement -->
<p id="erreur-email" role="alert" class="erreur"></p>

<script>
// À la soumission, injecter le message
document.getElementById('erreur-email').textContent =
  'Saisissez une adresse email valide.';
</script>
→ Dès l'injection, NVDA interrompt sa lecture et annonce :
  "Saisissez une adresse email valide."

La zone doit être vide au chargement de la page. Les AT ne surveillent que les live regions existantes dans le DOM. Si la zone est créée et remplie simultanément, l’annonce est manquée.

role="alert" est assertive : il interrompt. Pour les confirmations non urgentes (formulaire envoyé avec succès), préférer role="status" (polite) qui attend la fin de la lecture en cours.

Déplacer le focus après soumission

Quand un formulaire est soumis et que des erreurs sont détectées, le focus doit être déplacé pour que l’utilisateur sache quoi corriger.

Option A : Focus sur le premier champ en erreur

// Après validation, déplacer sur le premier champ invalide
const premierChampErreur = document.querySelector('[aria-invalid="true"]');
if (premierChampErreur) {
  premierChampErreur.focus();
}

Simple et direct, l’utilisateur arrive immédiatement sur le premier problème à corriger.

Option B : Focus sur un récapitulatif des erreurs

Pour les formulaires longs avec plusieurs erreurs, afficher un récapitulatif en haut et y déplacer le focus :

<div id="recap-erreurs" tabindex="-1" role="alert">
  <h2>Le formulaire contient 2 erreurs :</h2>
  <ul>
    <li><a href="#cp">Code postal</a> : format invalide</li>
    <li><a href="#email">Email</a> : champ obligatoire</li>
  </ul>
</div>
const recap = document.getElementById('recap-erreurs');
recap.hidden = false;
recap.focus(); // tabindex="-1" permet le focus programmatique

Les liens dans le récapitulatif permettent de naviguer directement vers chaque champ en erreur.

L’ordre complet des opérations

function soumettre(event) {
  event.preventDefault();
  const erreurs = valider(); // retourne les champs en erreur

  if (erreurs.length === 0) {
    // Envoyer le formulaire
    return;
  }

  // 1. Marquer chaque champ en erreur
  erreurs.forEach(({ champ, message }) => {
    champ.setAttribute('aria-invalid', 'true');

    // 2. Injecter le message d'erreur dans l'élément lié par aria-describedby
    const idErreur = champ.getAttribute('aria-describedby')
      ?.split(' ')
      .find(id => id.startsWith('erreur-'));
    if (idErreur) {
      document.getElementById(idErreur).textContent = message;
    }
  });

  // 3. Déplacer le focus sur le premier champ en erreur
  erreurs[0].champ.focus();
}

function corrigerChamp(input) {
  // Retirer la marque d'erreur quand l'utilisateur corrige
  if (input.getAttribute('aria-invalid') === 'true') {
    // Revalider le champ
    if (estValide(input)) {
      input.setAttribute('aria-invalid', 'false');
      const idErreur = input.getAttribute('aria-describedby')
        ?.split(' ')
        .find(id => id.startsWith('erreur-'));
      if (idErreur) {
        document.getElementById(idErreur).textContent = '';
      }
    }
  }
}

Les indications obligatoires : critère 11.9 et 11.10

Avant de parler des erreurs, il faut signaler les champs obligatoires. Deux règles s’appliquent :

Signaler les champs obligatoires avant le formulaire :

<p>
  Les champs marqués d'un
  <span aria-hidden="true">*</span>
  <span class="sr-only">astérisque</span>
  sont obligatoires.
</p>

Marquer chaque champ obligatoire dans son label :

<label for="nom">
  Nom <span aria-hidden="true">*</span>
</label>
<input type="text" id="nom" required>

L’astérisque est masqué aux AT (aria-hidden="true") parce que NVDA annoce déjà “requis” via l’attribut required. Le texte “astérisque” en sr-only dans l’explication initiale assure que les utilisateurs AT comprennent ce que le symbole visuel signifie.

required natif suffit sur un <input> HTML. aria-required="true" en complément serait redondant. Les deux attributs communiquent la même information aux AT.aria-required est réservé aux composants custom qui ne peuvent pas porter l’attribut required nativement.

Ce que le RGAA exige

Critère 11.9 : Dans chaque formulaire, l’intitulé de chaque bouton est-il pertinent ?

Le bouton de soumission doit avoir un intitulé qui décrit l’action : “Envoyer le formulaire”, “Valider ma demande”, “S’inscrire”. Pas “OK”, “Valider” seul, ou une icône sans texte alternatif.

Critère 11.10 : Dans chaque formulaire, le contrôle de saisie est-il utilisé de manière pertinente ?

Les indications de format, les champs obligatoires, et les suggestions de correction doivent être fournis avant ou pendant la saisie, pas seulement après soumission.

Critère 11.11 : Dans chaque formulaire, les messages d’erreur sont-ils pertinents ?

Un message d’erreur doit expliquer le problème et indiquer comment le corriger. “Erreur”, “Champ invalide”, “Valeur incorrecte”, ces messages existent mais ne sont pas pertinents. “Le code postal doit contenir exactement 5 chiffres (ex. 29770)” est pertinent.

La lettre de l'Atelier A11Y

Ressources pédagogiques, critères RGAA commentés et retours de terrain : une lettre mensuelle pour progresser sur l'accessibilité numérique, sans jargon.

  • Nouveaux articles et ressources pédagogiques
  • Critères RGAA décortiqués avec des exemples concrets
  • Bonnes pratiques et retours d'expérience terrain
S'abonner à la newsletter (s'ouvre dans un nouvel onglet)

Gratuit. Désabonnement possible à tout moment.