« Form elements must have labels » : l’extension axe DevTools affiche cette erreur sur un champ de recherche qui possède pourtant, à l’écran, un texte « Rechercher » juste au-dessus. Le développeur qui découvre ce message pour la première fois soupçonne un bug de l’outil. Ce n’est presque jamais le cas : axe ne juge pas la présence visuelle d’un texte, mais l’existence d’une association programmatique entre ce texte et le champ concerné.
Symptôme
Le code source du champ ressemble à ceci :
<span class="libelle-champ">Rechercher</span>
<input type="search" name="s" placeholder="Tapez votre recherche">
Visuellement, tout est parfaitement lisible : le texte « Rechercher » précède le champ, le style CSS les rapproche visuellement. Mais un lecteur d’écran qui atteint ce champ n’annonce que « champ de recherche », sans jamais mentionner « Rechercher », car aucune relation technique ne relie le <span> au champ <input>.
Diagnostic

Un lecteur d’écran, comme un outil d’audit automatisé, ne peut pas déduire une relation entre deux éléments à partir de leur seule proximité visuelle dans le CSS. Il lui faut une relation explicite dans le HTML : soit un véritable élément <label> avec un attribut for pointant vers l’id exact du champ, soit un attribut aria-label ou aria-labelledby directement posé sur le champ. Un <span> stylé pour ressembler à un label n’en est pas un aux yeux de la technologie d’assistance : c’est un texte flottant, sans lien programmatique avec quoi que ce soit.
La confusion vient souvent d’un habillage CSS trop poussé, où un designer ou développeur a recréé visuellement l’apparence d’un label sans utiliser la véritable balise sémantique, parfois pour contourner des contraintes de mise en page héritées d’un framework CSS.
Correctif
La correction la plus robuste consiste à utiliser un véritable <label> associé par for/id :
<label for="recherche-site">Rechercher</label>
<input type="search" id="recherche-site" name="s" placeholder="Tapez votre recherche">
L’attribut for doit correspondre exactement à l’id du champ, caractère pour caractère, y compris la casse. Une erreur fréquente consiste à générer dynamiquement les id côté PHP (via un compteur de boucle sur un widget de recherche répété) sans s’assurer que le for du label suit la même génération, ce qui provoque une désynchronisation silencieuse dès le deuxième champ affiché sur la page.
Lorsqu’un vrai <label> n’est techniquement pas envisageable (composant tiers qui génère son propre balisage, contrainte de plugin WooCommerce), une alternative valable consiste à poser aria-label directement sur le champ :
<input type="search" name="s" aria-label="Rechercher sur le site" placeholder="Tapez votre recherche">
Vérification
Après correction, relancer axe DevTools doit faire disparaître l’anomalie. Un second test, plus fiable encore, consiste à activer un lecteur d’écran (NVDA ou VoiceOver) et vérifier que le nom annoncé lors de l’arrivée sur le champ correspond bien au libellé attendu, et non simplement au type de champ générique.
Cas particuliers à surveiller
- Les champs générés par des blocs Gutenberg de formulaire, où l’
idpeut être dupliqué si le même bloc est utilisé deux fois sur la même page. - Les champs de recherche WooCommerce natifs, dont le
placeholderseul ne suffit jamais comme étiquette, car il disparaît dès la saisie du premier caractère. - Les formulaires générés par des extensions tierces (Contact Form 7, Gravity Forms) où le générateur visuel peut, selon la configuration, oublier de poser le
forcorrespondant.
Un texte visuellement proche d’un champ n’est un label que si le code le déclare explicitement : la proximité à l’écran ne vaut jamais association programmatique.
En résumé
L’erreur axe « Form elements must have labels » ne ment jamais, même lorsqu’un label est visible à l’écran : elle signale l’absence d’une relation technique explicite entre le texte et le champ. Un <label for> correctement associé, ou à défaut un aria-label, corrige systématiquement ce problème, à condition de vérifier la correspondance exacte des identifiants sur chaque champ généré dynamiquement.