Développement web

Formulaire HTML accessible : le faire bien sans framework

J'ai passé une après-midi sur un formulaire de contact qui avait l'air fini. Le bouton envoyait, le mail partait, la maquette collait au pixel près. J'ai lâché la souris. Tabulation, encore une tabulation, et le curseur tombait dans un rectangle sans nom. Le message d'erreur s'allumait en rouge tout

Formulaire HTML accessible : le faire bien sans framework

J'ai passé une après-midi sur un formulaire de contact qui avait l'air fini. Le bouton envoyait, le mail partait, la maquette collait au pixel près. J'ai lâché la souris. Tabulation, encore une tabulation, et le curseur tombait dans un rectangle sans nom. Le message d'erreur s'allumait en rouge tout en haut. Impossible de savoir quel champ corriger.

Ce jour-là, le HTML a repris la main. Un formulaire HTML accessible se joue dans les balises, le texte des erreurs et l'ordre du clavier. La feuille de style vient après. Le framework aussi, le jour où il sert.

Les points à retenir

  • Chaque champ visible porte un label relié par les attributs for et id.
  • Les champs d'une même question vivent dans un fieldset, avec une legend.
  • L'erreur dit quoi changer, reste près du champ, et lui est reliée dans le code.
  • Le navigateur aide à saisir. Le serveur décide d'accepter ou de refuser.
  • On valide le parcours au clavier, focus visible compris, avant de figer le design.
  • Le placeholder donne un exemple. Le nom du champ reste affiché.

Ce que la souris cache

La souris pardonne beaucoup. On clique où l'œil devine le champ, on voit un halo rouge, on suppose qu'il vise l'e-mail encore sous le pointeur. Au clavier, ces indices tombent d'un coup.

Le test que je relance à chaque reprise

Je charge la page. La souris reste de côté jusqu'à l'envoi, ou jusqu'au premier blocage. Je note quatre choses au passage : le nom de chaque champ, l'endroit de l'erreur, la visibilité du focus, le libellé du bouton.

La première fois, sur un formulaire maison, j'ai croisé des champs sans étiquette et un groupe de cases dont le titre vivait seulement dans la feuille de style. Le bouton disait OK. La maquette avait l'air propre. Au clavier, c'était un couloir sans portes.

Qui se retrouve bloqué

Ce parcours concerne les personnes qui naviguent sans souris, et les lecteurs d'écran qui suivent le focus. Un zoom fort peut pousser le message d'erreur hors de l'écran. Un téléphone saisi au mauvais format a besoin d'une phrase à côté du champ, lisible tout de suite.

J'y ajoute le développeur dans six mois. Des balises nommées se relisent. Une page dont le sens tient seulement dans le placement visuel, beaucoup moins.

Poser les balises dans le bon ordre

Un formulaire est une suite de questions. Le HTML doit les garder lisibles si la feuille de style disparaît. J'écris d'abord le formulaire nu, dans le navigateur. Quand les questions se comprennent sans décor, j'habille.

Le couple label, for et id

Chaque champ a un nom visible, dans un élément label. L'attribut for reprend l'id du champ. Même valeur des deux côtés, unique dans la page.

Le clic sur le nom donne le focus, et la zone cliquable grandit d'autant. Un lecteur d'écran annonce ce nom quand le focus arrive. Le couple survit à un changement de grille, de colonne ou de marge. Le texte du label reste affiché pendant la frappe, donc la personne sait encore ce qu'elle est en train de remplir.

Je prends des id stables : email, telephone, message. Les id du genre champ-1 ou input-2 pourrissent vite. Six mois plus tard, plus personne ne sait ce que champ-2 désignait. Moi le premier.

Le placeholder montre un exemple, du genre [email protected]. Il disparaît à la première lettre. Je le garde pour illustrer le format. Le label reste à l'écran.

Fieldset et legend pour les groupes

Madame et Monsieur, c'est une seule question, avec deux réponses. Le fieldset entoure les cases. La legend énonce la question : Civilité. Une adresse découpée en rue, code postal et ville, ou un choix de créneau, suit le même traitement.

Sans legend, chaque case part de son côté et la question sort du code. J'ai déjà mimé une légende avec un div stylé. L'écran suivait la maquette. Le lecteur d'écran listait des cases sans sujet. Le fieldset a remis la question au bon endroit, sans une ligne de script.

Écrire des erreurs qu'on peut corriger

Une erreur utile dit le geste à faire : quoi changer, dans ce champ, maintenant.

« Champ invalide » laisse la personne seule. « L'e-mail doit contenir un @ et un nom de domaine, par exemple [email protected] » montre la voie.

Le texte dit quoi modifier

La phrase reste courte, collée au champ concerné. Le nom du champ y figure, pour le cas où le message serait relu hors de son bloc. Si le format coince, le format attendu est écrit dans la phrase.

Le rouge peut renforcer l'alerte. L'information tient dans les mots. En niveaux de gris, la phrase doit encore suffire toute seule, parce que le halo a disparu avec la couleur.

Le ton reste calme. « E-mail manquant » suffit. Une formule dure, envoyée à 23 h, n'aide pas à terminer. Elle se relit mal, des semaines plus tard, dans un ticket de support.

Relier le message au champ

La phrase d'erreur a un id. Le champ pointe vers cet id avec aria-describedby. Au focus, le nom du champ et l'erreur arrivent ensemble.

aria-invalid="true" marque l'échec. Je retire l'attribut dès que la saisie redevient acceptable. Un champ encore annoncé invalide après correction pousse à retaper une valeur qui était déjà bonne.

Pour une erreur de page entière, du genre « le serveur n'a pas répondu, vous pouvez réessayer », je place le texte en tête du formulaire et je lui donne le focus. Sinon le message est visible, et le clavier passe à côté.

Ces textes vivent dans le HTML, près des champs. Un message injecté ailleurs par un script disparaît avec le script. Le formulaire nu doit déjà savoir parler.

Le navigateur contrôle une forme. Il ignore si l'adresse existe vraiment, si le créneau est encore libre, si la personne a le droit d'envoyer. Un e-mail accepté à l'écran peut viser un domaine sans aucune boîte. Ça m'est arrivé sur un petit formulaire de newsletter. La validation serveur a été la seule à le voir.

Navigateur Serveur
Moment Avant l'envoi, chez la personne À la réception de la requête
Ce qu'il peut voir Présence, type, longueur, motif La saisie, les règles métier, les données déjà enregistrées
Ce qui lui échappe La base, les droits, les doublons, les quotas Rien de fiable : la requête se revérifie en entier
Si on le contourne Un script, ou l'attribut formnovalidate, saute le filtre Toute charge est acceptée
Message rendu Bulle native, ou phrase écrite dans la page Phrase renvoyée avec la page ou avec la réponse

Les attributs que le navigateur comprend déjà

required refuse le champ vide. type="email" et type="url" vérifient une forme assez large, parfois plus souple que l'idée de départ. minlength, maxlength, min et max posent des bornes. pattern accepte une expression régulière : je la garde souple, parce qu'une expression trop serrée rejette des noms et des numéros parfaitement réels.

autocomplete="email", autocomplete="tel" et autocomplete="name" permettent au navigateur de proposer une valeur déjà connue. J'ai repris un formulaire de livraison où chaque champ portait autocomplete="off", copié par réflexe. L'adresse se retapait à chaque commande. Je réserve cette coupure à un code à usage unique.

Le bouton d'envoi est un button de type submit, ou un input du même type. Un div cliquable, branché sur un script, rate souvent la touche Entrée. Il sort aussi, parfois, de l'ordre naturel du clavier.

La règle qui reste côté serveur

Toute contrainte montrée dans la page est refaite à l'arrivée. Le HTML et le JavaScript restent une aide pour la personne. La requête compte comme une donnée quelconque, y compris quand elle a été composée sans jamais afficher le formulaire.

Le serveur renvoie alors la même phrase utile, rattachée au même champ. La page conserve ce qui a déjà été saisi. Mot de passe et code repartent vides. Retaper vingt lignes pour un code postal refusé use la patience de n'importe qui : je prévois le renvoi des valeurs dès que j'écris le traitement.

Tenir le parcours jusqu'au bouton

Des balises justes peuvent encore se dérober. Focus invisible. Tabulation qui saute du nom au bouton puis revient au prénom. Lien Annuler collé si près de l'envoi qu'une validation part trop vite.

Un focus qu'on voit vraiment

outline: none sur toute la page, je l'ai posé autrefois pour faire taire un liseré bleu qui jurait avec la maquette. Le clavier est devenu une promenade à l'aveugle. J'ai remis l'indicateur le soir même.

:focus-visible montre le repère quand la personne arrive au clavier, et le laisse plus discret au clic souris. Épaisseur, couleur, petit décalage : le contraste doit tenir sur fond clair et sur fond foncé, si le site propose les deux.

La taille des contrôles joue dans la même scène. Un champ trop bas, un bouton minuscule, et le doigt rate la cible sur un écran tactile. Je laisse de l'air autour. Le texte du bouton nomme l'action : « Envoyer le message », « Créer le compte ». « OK » tout seul oblige à relire la page pour savoir ce qu'on déclenche.

L'ordre de tabulation suit la page

L'ordre du HTML est le bon, tant que la mise en page ne déplace pas un bloc à l'écran sans le déplacer dans la source. Une colonne affichée en premier alors qu'elle arrive en second dans le code, et l'œil contredit le clavier. La personne a l'impression de sauter des cases.

J'évite les tabindex supérieurs à zéro. Un nombre positif réécrit tout le parcours, et le moindre champ inséré ensuite devient un casse-tête. tabindex="0" reste rare, pour un composant vraiment à part. tabindex="-1" sert à envoyer le focus, par script, vers la zone d'erreur globale, sans l'ajouter dans la tabulation de tous les jours.

Après un passage complet, une question suffit. Est-ce que j'ai pu remplir, corriger et envoyer sans deviner ? Si oui, la couche visuelle peut arriver. Si je cherche encore, je corrige le HTML avant d'ouvrir la feuille de style.

Le formulaire que je relis six mois plus tard

Le code que je rouvre avec plaisir se lit à voix haute. Chaque champ a son nom. Chaque groupe a sa question. Chaque erreur dit le geste. La validation côté serveur répète ces règles, après le premier filtre qu'offre le navigateur.

Les outils savent générer cette ossature quand les réflexes sont déjà là. Écrite une fois à la main, elle devient le modèle, et l'outil n'a plus qu'à la reproduire.

La prochaine fois qu'un champ arrive sur une page, je commence par son label. Le reste du travail s'aligne derrière, et la personne en face aussi.

Questions fréquentes

Faut-il des attributs ARIA sur chaque champ ?

Pour un champ classique, le label relié suffit, avec un fieldset et une legend dès qu'il y a un groupe. ARIA sert à l'état d'erreur, via aria-invalid et aria-describedby, et à un composant sans balise native, comme une liste fabriquée entièrement en div. Un rôle posé sur un vrai élément input produit souvent une annonce en double.

Un placeholder peut-il remplacer le label ?

Il montre un exemple de saisie, et il s'efface dès la première frappe. Le nom du champ a besoin d'un label qui reste visible pendant toute la saisie. Les deux vivent très bien ensemble : l'un nomme, l'autre illustre.

La validation du navigateur protège-t-elle le formulaire ?

Elle guide la saisie et elle évite une partie des allers-retours. Format, droits, doublons et limites se revérifient sur le serveur, avec des phrases aussi claires que celles affichées dans la page.

Comment tester sans lecteur d'écran ?

Parcourez le formulaire au clavier seul. Chaque champ doit avoir un nom visible, chaque erreur une phrase proche du champ, et le focus doit se voir. Zoomez le texte. Passez l'affichage en niveaux de gris, pour vérifier que les mots suffisent quand la couleur disparaît. Le lecteur d'écran vient après ce socle, pour contrôler les annonces.

Romain Rousseau

Romain Rousseau

Romain Rousseau est journaliste spécialisé dans l'actualité technologique, l'informatique et les objets connectés. Depuis plus de huit ans, il couvre les évolutions des logiciels, les innovations matérielles et les tendances du secteur. Son travail consiste à décrypter les enjeux techniques pour un public large, en privilégiant une approche pratique et accessible.

Voir tous les articles →