# Construire un accordéon accessible au clavier sans bibliothèque JavaScript

> Un accordéon de FAQ mal codé ignore le clavier et les lecteurs d'écran. Voici comment en construire un, entièrement en JavaScript natif, en suivant le motif ARIA officiel.

- Auteur : Clément Hadrot
- Publié le : 2020-02-25
- Mis à jour le : 2020-02-25
- Catégorie : Accessibilité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/accessibilite/accordeon-accessible-clavier-javascript-natif/

## L’essentiel

- aria-expanded sur chaque bouton
- Flèches haut/bas pour naviguer entre en-têtes
- Aucune dépendance, 60 lignes de JS

Un client m'a un jour envoyé une capture d'écran de sa FAQ : douze questions empilées dans un accordéon acheté sur une place de marché de plugins. Au clavier, impossible d'ouvrir la moindre réponse — seul le clic à la souris fonctionnait. Le plugin avait été codé avec des `<div>` cliquables et aucun gestionnaire de touche. Plutôt que de chercher une alternative payante, nous avons reconstruit l'accordéon à la main, sans bibliothèque, en suivant le motif de conception publié par le W3C.

La bonne nouvelle : un accordéon accessible ne demande ni React ni jQuery. Il tient dans un HTML sobre, un peu de CSS et une soixantaine de lignes de JavaScript natif. La difficulté n'est pas la quantité de code mais l'exactitude des attributs ARIA et de la gestion du focus.

## Le HTML de départ

Chaque en-tête d'accordéon est un vrai `<button>`, jamais un `<div>` ni un `<span>` : c'est ce qui donne gratuitement le focus clavier, l'activation par Entrée et Espace, et le rôle `button`.

```
<h3>
  <button type="button" class="accordion-trigger" aria-expanded="false" aria-controls="panel-livraison">
    Quels sont les délais de livraison ?
  </button>
</h3>
<div id="panel-livraison" class="accordion-panel" role="region" aria-labelledby="panel-livraison-label" hidden>
  <p>Les commandes passées avant 14 h sont expédiées le jour même.</p>
</div>
```

L'attribut `aria-expanded` reflète l'état ouvert ou fermé du panneau associé, désigné par `aria-controls`. L'attribut `hidden` masque le contenu du panneau à tout le monde, y compris aux lecteurs d'écran, tant qu'il n'est pas ouvert — c'est plus fiable qu'un simple `display: none` posé en CSS seul, car il reste vrai même si une feuille de style tarde à charger.

## Le script : ouverture, fermeture, navigation entre en-têtes

> L'essentiel à retenir : aria-expanded sur chaque bouton ; Flèches haut/bas pour naviguer entre en-têtes ; Aucune dépendance, 60 lignes de JS

Le script écoute les clics et les touches sur chaque bouton d'en-tête. Les flèches Haut et Bas déplacent le focus vers l'en-tête précédent ou suivant, comme le préconise le motif ARIA « accordion » :

```
const triggers = document.querySelectorAll('.accordion-trigger');

triggers.forEach((trigger, index) => {
  trigger.addEventListener('click', () => toggle(trigger));
  trigger.addEventListener('keydown', (event) => {
    if (event.key === 'ArrowDown') {
      event.preventDefault();
      const next = triggers[index + 1] || triggers[0];
      next.focus();
    }
    if (event.key === 'ArrowUp') {
      event.preventDefault();
      const prev = triggers[index - 1] || triggers[triggers.length - 1];
      prev.focus();
    }
  });
});

function toggle(trigger) {
  const expanded = trigger.getAttribute('aria-expanded') === 'true';
  const panel = document.getElementById(trigger.getAttribute('aria-controls'));
  trigger.setAttribute('aria-expanded', String(!expanded));
  panel.hidden = expanded;
}
```

Ce script suffit pour un accordéon dit « exclusif optionnel » : chaque panneau s'ouvre et se ferme indépendamment, plusieurs peuvent rester ouverts en même temps. Si le besoin est qu'un seul panneau reste ouvert à la fois, il suffit de fermer les autres boutons dans la fonction `toggle` avant d'ouvrir le nouveau.

## Ce qu'il ne faut surtout pas faire

- Utiliser `aria-hidden` sur le panneau plutôt que `hidden` : `aria-hidden="true"` masque le contenu aux technologies d'assistance, mais un utilisateur de clavier seul (sans lecteur d'écran) pourrait encore tabuler jusqu'à des liens invisibles à l'écran.
- Animer l'ouverture avec une hauteur calculée en JavaScript sans respecter `prefers-reduced-motion` : certains utilisateurs souffrent de troubles vestibulaires déclenchés par ces transitions.
- Rendre les en-têtes focusables au `<div tabindex="0">` plutôt qu'à de vrais boutons : le clavier fonctionnera peut-être, mais l'activation par Espace et l'annonce du rôle demanderont un travail supplémentaire inutile.

## Intégrer ça dans un contexte WordPress

Dans un thème classique, ce script se charge simplement via `wp_enqueue_script()` avec l'attribut `defer`, sans dépendance sur jQuery. Pour un accordéon inséré depuis l'éditeur de blocs, le même script peut être attaché à un bloc personnalisé enregistré avec `register_block_type()`, le rendu du bloc produisant directement le HTML présenté plus haut.

## Pour aller plus loin

Le motif complet, avec ses variantes (accordéon à panneau unique, en-têtes de niveaux de titre différents), est documenté par le [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/). C'est la référence à garder sous la main avant d'écrire le moindre composant interactif fait maison, accordéon ou non.
