RGAA Lab

Composants accessibles

L'accordéon

Un accordéon accessible tient en trois choses : un bouton natif, un état annoncé, un panneau vraiment masqué. Aucun rôle ARIA à inventer. Démo, code, critères RGAA.

4 critères RGAA en jeu

Essayez-le

Parcourez-le à la tabulation : le focus s’arrête sur chaque intitulé et sur rien d’autre tant que les panneaux sont repliés. Ouvrez-en un, et son contenu entre alors dans l’ordre de tabulation — ni avant, ni après.

Cette démo existe aussi en page autonome.

L’erreur courante

L’accordéon bricolé, c’est un div qui écoute le clic et une classe CSS qui réduit la hauteur.

<!-- À ne pas faire -->
<div class="titre" onclick="basculer()">Faut-il une capture ?</div>
<div class="panneau replie">
	<p>Oui. Un rapport doit se relire des mois plus tard…</p>
</div>
/* À ne pas faire : le contenu est toujours là, et toujours focalisable */
.replie {
	height: 0;
	overflow: hidden;
}

Deux fautes, et la seconde est la plus sournoise.

Le div cliquable ne prend pas le focus, ne répond ni à Entrée ni à

Espace, et n'annonce ni sa nature de bouton ni son état. Un utilisateur au clavier ne peut pas ouvrir le panneau ; un lecteur d'écran ne sait pas qu'il y a quelque chose à ouvrir.

Mais le panneau « replié » à hauteur nulle, lui, est toujours là. Ses liens et ses boutons restent dans l’ordre de tabulation : le focus disparaît de l’écran, se promène dans un contenu invisible, puis réapparaît plus bas. C’est le défaut le plus déroutant qui soit pour qui navigue au clavier — et il est invisible à la souris.

Le code, pas à pas

1. Un bouton, dans un titre

<h3>
	<button type="button" aria-expanded="false" aria-controls="panneau-preuve">
		Faut-il une capture pour chaque non-conformité ?
	</button>
</h3>

<div id="panneau-preuve" hidden>
	<p>Oui. Un rapport doit se relire des mois plus tard…</p>
</div>

Le <button> donne le focus, Entrée, Espace et l’annonce « bouton » — quatre comportements qu’on ne réécrit pas.

Le bouton se place dans le titre, jamais le titre dans le bouton. Ainsi la structure de titres reste intacte, et un lecteur d’écran peut sauter d’un intitulé au suivant par la navigation par titres, sans passer par la tabulation. Le niveau (h3 ici) suit celui de la section qui contient l’accordéon.

2. L’état : aria-expanded

bouton.setAttribute('aria-expanded', String(ouvert));
panneau.hidden = !ouvert;

aria-expanded est l’état, et le lecteur d’écran l’annonce avec le nom : « Faut-il une capture ?, bouton, réduit ». Le chevron qui tourne dit la même chose à l’œil — c’est un doublon utile, à condition qu’il soit masqué (aria-hidden="true") pour ne pas être annoncé en plus.

aria-controls désigne le panneau gouverné. Son effet reste inégal d’un lecteur à l’autre, mais il ne coûte rien et documente l’intention.

3. Le panneau : hidden, pas une hauteur nulle

<div id="panneau-preuve" hidden></div>

hidden retire le panneau de l’affichage et de l’ordre de tabulation, en un attribut. C’est ce qui règle le piège du focus perdu.

Si vous voulez animer l’ouverture, hidden et la transition de hauteur ne se marient pas d’eux-mêmes. Deux options honnêtes : content-visibility avec transition-behavior: allow-discrete, ou l’élément natif <details> (voir plus bas). Ce qu’il ne faut pas faire, c’est renoncer à masquer pour pouvoir animer.

4. Un panneau ouvert n’en ferme aucun autre

Un accordéon laisse ouvrir plusieurs panneaux à la fois. Refermer les autres à chaque ouverture surprend qui vient de lire, et interdit de comparer deux réponses — n’imposez ce comportement que si le besoin l’exige vraiment.

5. <details>, quand il suffit

<details>
	<summary>Faut-il une capture pour chaque non-conformité ?</summary>
	<p>Oui. Un rapport doit se relire des mois plus tard…</p>
</details>

Le navigateur donne alors tout : le focus, le clavier, l’état annoncé, le masquage réel. C’est la meilleure réponse quand l’apparence par défaut convient et qu’aucun titre n’est nécessaire — ce qui est plus souvent le cas qu’on ne croit. Ses limites : le style de <summary> reste contraint, et il n’y a pas de titre à porter, donc pas de navigation par titres.

Au clavier

ToucheEffet
TabPasse à l’intitulé suivant. Le contenu d’un panneau replié n’y figure pas.
Entrée / EspaceOuvre ou ferme le panneau de l’intitulé qui a le focus.
Navigation par titresSaute d’un intitulé à l’autre sans tabuler, parce que le bouton est dans un titre.

Les flèches ne servent pas ici : contrairement aux onglets, un accordéon est une suite de sections indépendantes, pas un groupe où l’on choisit.

Ce que le lecteur d’écran restitue

Sur l’intitulé : le nom, la nature, l’état — « Faut-il une capture pour chaque non-conformité ?, bouton, réduit ». Après activation, l’état change et se réannonce : « développé ».

Si vous entendez le nom sans l’état, aria-expanded manque ou reste figé. Si vous n’entendez pas « bouton », c’est un div — et personne ne peut l’ouvrir au clavier.

Checklist

  • Un <button type="button">, jamais un div cliquable.
  • Le bouton dans le titre, à un niveau cohérent avec la page.
  • aria-expanded tenu à jour ; aria-controls vers le panneau.
  • Le chevron doublé de l’état, et masqué par aria-hidden.
  • Le panneau replié masqué par hidden — jamais par une hauteur nulle.
  • Envisagez <details> avant d’écrire du JavaScript.

Les critères RGAA en jeu

Vérifiez votre composant avec l'extension

Pour aller plus loin

Sources