vendredi 25 septembre 2026

À propos

Contact

Accessibilité

Une case à cocher personnalisée invisible pour un lecteur d’écran : le correctif

Une case à cocher stylée en CSS peut devenir totalement invisible pour un lecteur d'écran. Symptôme, diagnostic et correctif d'un piège très répandu sur les formulaires WordPress.

Par Clément Hadrot • 19 mars 2020 • 4 min de lecture • Aucun commentaire
Une case à cocher personnalisée invisible pour un lecteur d'écran : le correctif

Le ticket disait simplement : « impossible de cocher la case d’acceptation des conditions générales avec NVDA ». Le formulaire fonctionnait pourtant très bien à la souris : un joli carré personnalisé, coché d’une animation de coche verte au clic. Le problème est apparu dès l’inspection du DOM : la case à cocher native, celle que le navigateur et les lecteurs d’écran savent réellement manipuler, était masquée avec display: none.

C’est un piège extrêmement courant dès qu’un designer souhaite une case à cocher qui ne ressemble pas à celle, assez rustique, que dessine chaque navigateur. La solution consiste presque toujours à masquer l’élément natif pour le remplacer visuellement par un <span> stylé. Le problème n’est pas l’intention, mais la méthode de masquage choisie.

Symptôme : la case existe dans le DOM mais reste muette

En navigant au clavier avec Tab, le focus saute la case entièrement : aucun arrêt, aucune annonce vocale. Dans l’arbre d’accessibilité de Firefox, l’élément <input type="checkbox"> apparaît carrément absent de la liste des objets accessibles, alors qu’il est bien présent dans le HTML source.

.custom-checkbox input[type="checkbox"] {
  display: none;
}
.custom-checkbox .fake-box {
  width: 20px;
  height: 20px;
  border: 2px solid #333;
  display: inline-block;
}

Voilà le coupable. La propriété display: none retire l’élément de l’arbre d’accessibilité au même titre que du rendu visuel. Un lecteur d’écran ne peut pas annoncer, ni même détecter, ce qui n’existe pas dans cet arbre. Le <span> visuel, lui, n’est ni focusable ni interprété comme une case à cocher : c’est un rectangle décoratif, point final.

Diagnostic : distinguer masquage visuel et masquage total

L'essentiel à retenir : display none masque aussi pour les lecteurs d'écran ; Utiliser un positionnement hors-écran, pas none ; Vérifier avec NVDA après chaque correctif

Il existe une différence fondamentale entre « invisible à l’écran » et « invisible pour tout le monde » :

  • display: none et visibility: hidden retirent l’élément de l’arbre d’accessibilité : c’est un masquage total.
  • aria-hidden="true" fait la même chose, indépendamment du rendu visuel : à éviter sur un élément interactif.
  • Un positionnement hors du cadre visible (technique dite visually hidden) masque uniquement à l’écran, en laissant l’élément focusable et lisible par les lecteurs d’écran.

Le correctif

La correction consiste à remplacer le masquage total par un positionnement hors-écran, qui conserve l’élément natif dans le flux du clavier et de l’arbre d’accessibilité :

.custom-checkbox input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

Cette combinaison de propriétés — connue sous le nom de classe utilitaire .screen-reader-text dans le cœur de WordPress — réduit l’élément à un pixel tout en le laissant réellement présent et manipulable. Le <span> décoratif reste affiché visuellement, mais il faut aussi s’assurer que le focus visuel (le contour bleu par défaut du navigateur) se reporte sur ce <span> quand l’input natif reçoit le focus, via un sélecteur :focus + .fake-box.

input[type="checkbox"]:focus + .fake-box {
  outline: 2px solid #1b5fc1;
  outline-offset: 2px;
}

Vérification

Après correctif, trois vérifications rapides suffisent : tabuler jusqu’à la case au clavier, appuyer sur Espace pour la cocher, puis relancer NVDA pour confirmer que l’état « coché » ou « non coché » est bien annoncé. Si le libellé associé n’est pas lu, vérifiez que le <label> utilise bien l’attribut for pointant vers l’id de l’input, ou que l’input est imbriqué dans le <label>.

Sur chaque nouveau composant de formulaire personnalisé, je pars désormais du principe que « masquer visuellement » et « supprimer » sont deux opérations différentes, et je vérifie systématiquement laquelle des deux le CSS applique réellement.

Prévention

Pour éviter que le problème ne revienne sur le prochain composant, une règle simple à ajouter aux revues de code du thème : toute recherche de display: none ou visibility: hidden ciblant un input, un button ou un a mérite une relecture attentive avant validation.

En résumé

Un carré personnalisé plus joli qu’une case à cocher native ne pose aucun problème en soi. Le risque apparaît au moment de masquer l’original : privilégiez toujours un masquage hors-écran plutôt qu’un display: none, et testez au clavier avant de considérer le composant terminé.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi