Accordéons accessibles : Guide d'implémentation complet
En Bref : L'essentiel à retenir
- Un accordéon accessible nécessite un bouton dans un heading, aria-expanded pour l'état, et aria-controls liant le bouton au panneau.
- La navigation clavier suit le pattern WAI-ARIA : Entrée/Espace pour basculer, flèches pour naviguer entre les en-têtes, Home/End pour les extrêmes.
- L'élément HTML détails/summary est une alternative native mais son support accessibilité reste incomplet dans certains lecteurs d'écran.
- Le panneau masqué doit utiliser l'attribut hidden plutôt que display:none pour une meilleure sémantique et compatibilité.
Les accordéons permettent d'organiser du contenu dense en sections repliables. Mais leur accessibilité est souvent négligée, rendant les informations inaccessibles aux utilisateurs de technologies d'assistance. Voici comment créer des accordéons conformes au pattern WAI-ARIA.
Anatomie d'un accordéon accessible
Un accordéon se compose de :
- En-têtes (headers) : boutons déclencheurs dans des headings
- Panneaux (panels) : contenus associés à chaque en-tête
- États : déplié/replié communiqués via ARIA
┌─────────────────────────────┐
│ [▼] Section 1 (bouton) │ ← En-tête h3
├─────────────────────────────┤
│ Contenu de la section 1... │ ← Panneau (visible)
│ │
├─────────────────────────────┤
│ [▶] Section 2 (bouton) │ ← En-tête h3
├─────────────────────────────┤
│ (contenu masqué) │ ← Panneau (hidden)
└─────────────────────────────┘
Structure HTML de base
<div class="accordion">
<h3>
<button
type="button"
aria-expanded="true"
aria-controls="panel-1"
id="header-1">
Qu'est-ce que l'accessibilité web ?
</button>
</h3>
<div
id="panel-1"
role="region"
aria-labelledby="header-1">
<p>L'accessibilité web permet aux personnes handicapées
d'utiliser le web de manière autonome...</p>
</div>
<h3>
<button
type="button"
aria-expanded="false"
aria-controls="panel-2"
id="header-2">
Pourquoi c'est important ?
</button>
</h3>
<div
id="panel-2"
role="region"
aria-labelledby="header-2"
hidden>
<p>En France, 12 millions de personnes sont en situation
de handicap...</p>
</div>
</div>
Explication des attributs
| Attribut | Élément | Rôle |
|---|---|---|
aria-expanded | Bouton | État déplié/replié |
aria-controls | Bouton | ID du panneau contrôlé |
role="region" | Panneau | Landmark navigable |
aria-labelledby | Panneau | Nommé par l'en-tête |
hidden | Panneau | Masque le contenu replié |
[!NOTE] Le
role="region"est optionnel et recommandé uniquement si votre accordéon a moins de 6 sections. Au-delà, cela crée trop de landmarks.
Navigation clavier complète
Le pattern WAI-ARIA définit ces interactions :
| Touche | Action |
|---|---|
| Tab | Passe au prochain élément focusable |
| Entrée/Espace | Bascule l'état déplié/replié |
| Flèche bas | Focus sur l'en-tête suivant |
| Flèche haut | Focus sur l'en-tête précédent |
| Home | Focus sur le premier en-tête |
| End | Focus sur le dernier en-tête |
Implémentation JavaScript
class Accordion {
constructor(container) {
this.container = container;
this.headers = container.querySelectorAll('button');
this.panels = container.querySelectorAll('[role="region"]');
this.init();
}
init() {
this.headers.forEach((header, index) => {
header.addEventListener('click', () => this.toggle(index));
header.addEventListener('keydown', (e) => this.handleKeydown(e, index));
});
}
toggle(index) {
const header = this.headers[index];
const panel = this.panels[index];
const isExpanded = header.getAttribute('aria-expanded') === 'true';
header.setAttribute('aria-expanded', !isExpanded);
panel.hidden = isExpanded;
}
handleKeydown(event, index) {
const { key } = event;
let newIndex;
switch (key) {
case 'ArrowDown':
event.preventDefault();
newIndex = (index + 1) % this.headers.length;
this.headers[newIndex].focus();
break;
case 'ArrowUp':
event.preventDefault();
newIndex = (index - 1 + this.headers.length) % this.headers.length;
this.headers[newIndex].focus();
break;
case 'Home':
event.preventDefault();
this.headers[0].focus();
break;
case 'End':
event.preventDefault();
this.headers[this.headers.length - 1].focus();
break;
}
}
}
// Initialisation
document.querySelectorAll('.accordion').forEach(accordion => {
new Accordion(accordion);
});
Variante : une seule section ouverte
Pour fermer automatiquement les autres sections :
toggle(index) {
const header = this.headers[index];
const panel = this.panels[index];
const isExpanded = header.getAttribute('aria-expanded') === 'true';
// Fermer toutes les sections
this.headers.forEach((h, i) => {
h.setAttribute('aria-expanded', 'false');
this.panels[i].hidden = true;
});
// Ouvrir la section cliquée (si elle était fermée)
if (!isExpanded) {
header.setAttribute('aria-expanded', 'true');
panel.hidden = false;
}
}
[!TIP] Permettre plusieurs sections ouvertes est souvent plus accessible : l'utilisateur ne perd pas le contenu qu'il vient de lire.
Styliser l'indicateur visuel
Utilisez CSS pour l'icône, et masquez-la aux lecteurs d'écran :
<button aria-expanded="false">
<span class="accordion-icon" aria-hidden="true"></span>
Titre de la section
</button>
.accordion-icon::before {
content: "+";
margin-right: 0.5em;
}
button[aria-expanded="true"] .accordion-icon::before {
content: "−";
}
L'état est communiqué par aria-expanded, l'icône est purement décorative.
Et details/summary natif ?
HTML5 propose <details> et <summary> pour les disclosures :
<details>
<summary>Cliquez pour voir plus</summary>
<p>Contenu révélé...</p>
</details>
Avantages
- Aucun JavaScript requis
- Support navigateur natif
Inconvénients actuels
- Pas d'annonce d'état cohérente dans tous les lecteurs d'écran
- Navigation clavier limitée (pas de flèches entre sections)
- Stylisation CSS plus complexe
Recommandation : Pour des accordéons simples non critiques, <details> peut suffire. Pour une conformité RGAA stricte, utilisez le pattern ARIA complet.
Consultez notre article sur aria-expanded pour plus de détails.
Gestion du focus au déploiement
Quand un panneau s'ouvre, le focus reste sur le bouton. L'utilisateur peut ensuite naviguer dans le contenu avec Tab ou les flèches en mode lecture.
Ne déplacez pas automatiquement le focus dans le panneau, sauf si c'est un comportement attendu (comme une modale).
Animer l'ouverture/fermeture
Si vous animez la transition, respectez prefers-reduced-motion :
.accordion [role="region"] {
transition: max-height 0.3s ease;
overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
.accordion [role="region"] {
transition: none;
}
}
// Alternative à hidden pour permettre l'animation
toggle(index) {
const panel = this.panels[index];
const isExpanded = header.getAttribute('aria-expanded') === 'true';
if (isExpanded) {
panel.style.maxHeight = null;
panel.setAttribute('aria-hidden', 'true');
} else {
panel.style.maxHeight = panel.scrollHeight + 'px';
panel.removeAttribute('aria-hidden');
}
}
Erreurs courantes
1. Bouton hors du heading
<!-- Incorrect : le bouton doit être dans le heading -->
<h3>Titre de section</h3>
<button aria-expanded="false">Voir plus</button>
<!-- Correct -->
<h3>
<button aria-expanded="false">Titre de section</button>
</h3>
2. Utiliser div au lieu de button
<!-- Incorrect : div n'est pas focusable nativement -->
<div class="accordion-header" onclick="toggle()">
Section
</div>
<!-- Correct : button natif -->
<button type="button" aria-expanded="false">
Section
</button>
3. Oublier d'initialiser aria-expanded
<!-- Incorrect : état indéterminé -->
<button>Section</button>
<!-- Correct : état initial défini -->
<button aria-expanded="false">Section</button>
Checklist accordéon accessible
- Boutons dans des éléments heading (h2-h6)
-
aria-expandedsur chaque bouton -
aria-controlspointant vers l'ID du panneau - Navigation clavier (Entrée, flèches, Home, End)
-
hiddenou équivalent sur les panneaux fermés - Icônes décoratives masquées (
aria-hidden) - Respect de
prefers-reduced-motion
Critères RGAA concernés
| Critère | Exigence |
|---|---|
| 7.1 | Composants compatibles avec les technologies d'assistance |
| 7.3 | Utilisables au clavier et à la souris |
| 10.7 | Pas de prise de focus inattendue |
| 12.8 | Navigation au clavier possible |
Conclusion
Un accordéon accessible demande du soin dans l'implémentation, mais le pattern est bien documenté par le WAI-ARIA. Suivez la structure HTML, implémentez la navigation clavier complète, et testez avec un lecteur d'écran pour valider l'expérience.
Vérifiez l'accessibilité de vos composants interactifs avec RGAA Checker : notre outil analyse les attributs ARIA et la structure de vos pages.
Guides RGAA associés
Pour aller plus loin sur les sujets abordés dans cet article, consultez nos fiches techniques :
Chaque champ de formulaire doit avoir une étiquette (label) qui lui est liée explicitement.
Chaque image porteuse d'information doit avoir une alternative textuelle pertinente via l'attribut alt. Les images décoratives doivent avoir un attribut alt vide.
L'ordre de tabulation clavier doit être cohérent avec l'ordre de lecture visuel et ne pas piéger le focus.
Articles similaires
Votre site est-il conforme ?
Ne prenez pas de risques avec l'accessibilité. Lancez un audit complet de votre site en quelques minutes et obtenez un rapport détaillé des corrections à apporter.