Le WordPress d'aujourd'hui, décodé pour les développeurs

Accessibilité

Accordéon masqué en CSS : pourquoi il reste annoncé aux lecteurs d’écran

Ce qu'on observe sur des foires aux questions d'assurance, pourquoi le CSS seul ne suffit pas à masquer un panneau, et la correction avec aria-expanded et l'attribut hidden.

Par WordPress Développement • 1 septembre 2024 • 4 min de lecture • Aucun commentaire
Accordéon masqué en CSS : pourquoi il reste annoncé aux lecteurs d'écran

Pourquoi un lecteur d’écran annonce-t-il le contenu d’un panneau d’accordéon fermé, alors que celui-ci est parfaitement invisible à l’écran ? C’est la question posée par l’équipe support d’un courtier en assurance auto, après plusieurs retours d’utilisateurs signalant une foire aux questions « qui répète tout en boucle » avec leur lecteur d’écran.

La FAQ comptait trente-quatre questions réparties en accordéon, chacune affichant sa réponse au clic sur l’intitulé de la question. Le composant avait été codé sur mesure quelques mois plus tôt, sans plugin, avec une animation de repli en CSS jugée suffisante par l’équipe qui l’avait développé.

Ce qu’on observe : un panneau fermé qui reste lu intégralement

Le code initial masquait le panneau de réponse uniquement via une classe CSS appliquant max-height: 0 et overflow: hidden, une technique courante pour animer l’ouverture et la fermeture en douceur.

.reponse {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}
.reponse.ouverte {
  max-height: 500px;
}

Visuellement, cette technique fonctionne parfaitement : le panneau fermé n’occupe aucune place visible et l’animation d’ouverture est fluide. Mais un lecteur d’écran ne s’appuie pas sur le rendu visuel pour décider quoi annoncer : il lit le contenu du DOM tel qu’il existe, indépendamment de sa hauteur CSS. Résultat, les trente-quatre réponses de la FAQ étaient toutes lues à la suite lors d’une navigation séquentielle, qu’elles soient ouvertes ou non à l’écran.

Pourquoi le CSS seul ne suffit jamais

Un contenu masqué par max-height: 0, opacity: 0 ou même visibility: hidden selon les cas reste présent dans l’arbre d’accessibilité du navigateur, sauf exception. Seules certaines propriétés précises, en particulier display: none ou l’attribut HTML natif hidden, retirent effectivement un élément de cet arbre et empêchent les technologies d’assistance de le restituer.

Le choix de max-height plutôt que display: none dans ce composant s’expliquait par la volonté d’animer la transition, une contrainte technique légitime, mais qui avait fait perdre de vue la conséquence sur l’accessibilité du composant.

L'essentiel à retenir : display none en CSS seul ne suffit jamais à masquer proprement ; aria-expanded décrit l'état, hidden masque réellement ; Les deux doivent rester synchronisés à chaque clic

Correctif : synchroniser aria-expanded et hidden

La correction retenue combine deux mécanismes distincts et complémentaires : aria-expanded sur le bouton de la question, qui décrit l’état ouvert ou fermé sans rien masquer, et l’attribut hidden sur le panneau de réponse, qui le retire réellement de l’arbre d’accessibilité quand il est fermé.

<h3>
  <button aria-expanded="false" aria-controls="reponse-12">
    Que couvre exactement la garantie bris de glace ?
  </button>
</h3>
<div id="reponse-12" class="reponse" hidden>
  <p>La garantie bris de glace couvre le pare-brise…</p>
</div>

Le script bascule les deux attributs ensemble à chaque clic, sans jamais laisser l’un des deux en retard sur l’autre : retirer hidden au moment de l’ouverture, puis laisser l’animation CSS existante gérer la transition visuelle sur un panneau désormais présent dans le DOM.

bouton.addEventListener('click', () => {
  const ouvert = bouton.getAttribute('aria-expanded') === 'true';
  bouton.setAttribute('aria-expanded', String(!ouvert));
  panneau.hidden = ouvert;
});

Ce que cette correction ne change pas

  • L’animation de repli en max-height reste utilisée pour l’effet visuel, en complément de hidden, pas à sa place.
  • La structure en <h3> suivie d’un bouton, déjà correcte dans le composant d’origine, n’a pas eu besoin d’être modifiée.
  • Le comportement au clic à la souris reste identique pour les utilisateurs qui n’utilisent aucune technologie d’assistance.

Un piège voisin à surveiller

Le même défaut se rencontre fréquemment avec des menus déroulants ou des info-bulles masqués uniquement par une opacité nulle associée à un positionnement hors écran : la technique visuelle fonctionne, mais le contenu reste pleinement présent et lu par les technologies d’assistance, exactement comme dans cet accordéon.

Un panneau invisible à l’œil n’est pas nécessairement invisible à l’arbre d’accessibilité : ce sont deux mécanismes distincts, et seul le second garantit qu’un contenu fermé reste réellement ignoré.

En résumé

Trente-quatre questions de FAQ étaient toutes lues intégralement par un lecteur d’écran, quel que soit leur état d’ouverture, à cause d’un masquage CSS qui ne retire jamais un élément de l’arbre d’accessibilité. La combinaison de aria-expanded, qui décrit l’état, et de hidden, qui masque réellement, résout ce défaut sans renoncer à l’animation existante, à condition de synchroniser les deux attributs à chaque interaction.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

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

Voir tous ses articles

Dans la même veine

À lire aussi