Des formulaires qui marchent vraiment
Validation au bon moment, messages d'erreur utiles, état de chargement et accessibilité, avec un exemple complet.
Un formulaire, c'est l'endroit où le visiteur fait un effort. Il tape, il attend, il espère que ça a marché. Chaque friction ajoutée à ce moment se paie en abandons, et les frictions sont presque toujours les mêmes : un message d'erreur qui apparaît avant qu'on ait fini de taper, un « Une erreur est survenue » qui ne dit rien, un bouton sur lequel on clique trois fois parce que rien n'indique que la première a été prise en compte.
Aucun de ces défauts n'est difficile à corriger. Ils viennent d'un enchaînement mal pensé, pas d'une limite technique.
Ce guide reprend cet enchaînement dans l'ordre où le visiteur le vit : saisie, validation, erreur, envoi, confirmation. Avec un exemple complet à la fin.
Avant de commencer
- Un formulaire existant, même minimal
- De quoi tester au clavier, sans souris
- Une idée de ce que tu fais des données une fois reçues
1Associer chaque champ à son libellé
C'est la base et c'est le défaut le plus fréquent. Un texte posé à côté d'un
champ n'est pas un libellé : il faut l'association explicite, par l'attribut
for de l'élément label qui reprend l'id du champ.
<label for="email">Adresse e-mail</label>
<input id="email" name="email" type="email" autocomplete="email" required />
Ce que ça débloque, au-delà de l'accessibilité : cliquer sur le libellé place le curseur dans le champ, ce qui agrandit nettement la cible sur mobile.
Le texte d'indication ne remplace pas le libellé
Un placeholder disparaît dès qu'on tape. Le visiteur qui revient vérifier sa
saisie ne sait plus ce qu'on lui demandait, et l'outil de remplissage
automatique n'a rien pour s'accrocher. Utilise-le pour un exemple de format,
jamais comme unique libellé.
Pense aussi à autocomplete avec la bonne valeur (email, name, tel,
postal-code, street-address). Le navigateur remplit alors le formulaire en
un geste, ce qui est le meilleur gain de conversion disponible pour un coût nul.
2Valider au bon moment
Le mauvais moment, c'est à chaque frappe. Un champ e-mail qui affiche « adresse invalide » dès la première lettre est agressif et inutile, puisque l'adresse est forcément incomplète.
Le bon moment est double : à la sortie du champ, puis à la soumission.
Le CSS sait faire cette distinction seul. Le pseudo-sélecteur :user-invalid
ne s'applique qu'après que le visiteur a interagi avec le champ ou tenté de
soumettre, contrairement à :invalid qui s'applique dès le chargement.
input:user-invalid {
border-color: #b3261e;
}
input:user-valid {
border-color: #2e6b41;
}
C'est disponible dans les navigateurs courants (référencé comme largement disponible depuis novembre 2023). Sur un projet devant couvrir des navigateurs plus anciens, il faut gérer l'état côté script.
3Écrire des messages d'erreur utiles
Un message d'erreur utile dit trois choses : quel champ, quel est le problème, quoi faire. La plupart des messages produits par défaut n'en disent qu'une.
| Message inutile | Message utile |
|---|---|
| « Champ invalide » | « L'adresse e-mail doit contenir un @, par exemple nom@domaine.fr » |
| « Erreur » | « Ce mot de passe fait 6 caractères, il en faut au moins 12 » |
| « Une erreur est survenue » | « Nous n'avons pas pu envoyer le message. Réessaie dans un instant, ou écris directement à contact@exemple.fr » |
| « Format incorrect » | « Le numéro doit contenir 10 chiffres, sans espace » |
Techniquement, un message d'erreur doit être relié au champ, pas seulement posé en dessous. Deux attributs ARIA le permettent :
aria-invalidsur le champ. Ses valeurs sonttrue,false,grammaretspelling; toute autre valeur est traitée commetrue.aria-describedbysur le champ, pointant vers l'idde l'élément qui contient le message.
aria-errormessage existe aussi
L'attribut aria-errormessage est prévu spécifiquement pour référencer un
message d'erreur, en complément de aria-invalid. Son support par les
lecteurs d'écran est plus inégal que celui de aria-describedby, qui reste le
choix le plus sûr en production. Si tu veux la ceinture et les bretelles, tu
peux poser les deux.
4Montrer que l'envoi est en cours
Sans retour visible, le visiteur clique à nouveau. Trois choses à faire pendant l'envoi :
- Désactiver le bouton, pour éviter le double envoi.
- Changer son texte, « Envoyer » devient « Envoi en cours ». Un indicateur qui tourne seul ne dit rien à qui ne le voit pas.
- Annoncer le résultat dans une zone que le lecteur d'écran surveille, avec
aria-live="polite"pour une confirmation, ourole="alert"pour une erreur, qui interrompt.
Le point que presque tout le monde oublie : après un envoi réussi, déplacer le focus sur le message de confirmation. Sinon le focus reste sur un bouton désactivé, et la personne qui navigue au clavier n'a aucun moyen de savoir ce qui s'est passé.
5Un exemple complet
Un champ, avec tout ce qui précède en place.
<form novalidate>
<div class="champ">
<label for="email">Adresse e-mail</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
required
aria-describedby="email-aide email-erreur"
/>
<p id="email-aide" class="aide">Pour te repondre. Rien d'autre.</p>
<p id="email-erreur" class="erreur" hidden></p>
</div>
<button type="submit">Envoyer</button>
<p id="statut" aria-live="polite"></p>
</form>
const formulaire = document.querySelector("form");
const champ = document.querySelector("#email");
const erreur = document.querySelector("#email-erreur");
const statut = document.querySelector("#statut");
const bouton = formulaire.querySelector("button");
function valider() {
if (champ.validity.valid) {
champ.removeAttribute("aria-invalid");
erreur.hidden = true;
return true;
}
champ.setAttribute("aria-invalid", "true");
erreur.textContent = champ.validity.valueMissing
? "Indique une adresse e-mail pour qu'on puisse te repondre."
: "L'adresse doit contenir un @, par exemple nom@domaine.fr";
erreur.hidden = false;
return false;
}
champ.addEventListener("blur", valider);
formulaire.addEventListener("submit", async (evenement) => {
evenement.preventDefault();
if (!valider()) {
champ.focus();
return;
}
bouton.disabled = true;
bouton.textContent = "Envoi en cours";
statut.textContent = "";
try {
await envoyer(new FormData(formulaire));
statut.textContent = "Message envoye. Reponse sous 48 heures.";
statut.focus();
} catch {
statut.textContent =
"Envoi impossible. Reessaie, ou ecris a contact@exemple.fr";
} finally {
bouton.disabled = false;
bouton.textContent = "Envoyer";
}
});
L'attribut novalidate désactive les bulles natives du navigateur pour te
laisser afficher tes propres messages. L'API de validation reste disponible :
champ.validity expose l'état sans que tu réécrives les règles.
La validation côté navigateur ne protège rien
Tout ce qui précède améliore l'expérience. Ce n'est pas une sécurité : les contraintes du navigateur se contournent en une ligne. Toute donnée reçue doit être revalidée côté serveur, sans exception.
Faire vérifier
Ouvre [URL]. Pour chaque champ du formulaire : la balise, le nom
accessible et d'ou il vient, la presence d'un attribut autocomplete, et
si un message d'erreur lui est relie par aria-describedby ou
aria-errormessage. Puis soumets le formulaire vide et dis-moi ou va le
focus et ce qui est annonce.
Quelques prompts pour aller plus loin
| Objectif | Prompt à adapter |
|---|---|
| Audit de libellés | Liste les champs de [URL] sans label associe, ou dont le seul libelle est un placeholder. |
| Réécrire les erreurs | Voici mes messages d'erreur. Reecris chacun pour dire quel champ, quel probleme et quoi faire, en moins de 15 mots. |
| Parcours clavier | Remplis et soumets ce formulaire uniquement au clavier. Dis-moi ou le focus se perd et a quelle etape. |
| Cas limites | Liste les saisies qui devraient etre rejetees par ce formulaire et ne le sont pas, cote navigateur comme cote serveur. |
| Remplissage automatique | Propose la valeur autocomplete correcte pour chaque champ de ce formulaire. |
Tu as d’autres ressources comme ça ?
Ce que tu viens de lire est entièrement gratuit. Une nouvelle ressource part chaque semaine dans la newsletter : guides, modèles à copier et liens triés, sans compte à créer.
Me contacter
Une question, un projet, une envie de collaborer ? Écris-moi.
Cette page est en accès libre, n’hésite pas à la partager.