# Fabriquer des onglets accessibles pour l’éditeur de blocs sans plugin dédié

> Plutôt qu'un plugin d'onglets de plus, voici comment enregistrer un bloc personnalisé qui produit des onglets accessibles, conformes au motif ARIA tabs, directement dans l'éditeur.

- Auteur : Clément Hadrot
- Publié le : 2021-08-30
- Mis à jour le : 2021-08-30
- Catégorie : Accessibilité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/accessibilite/onglets-accessibles-editeur-blocs-sans-plugin/

## L’essentiel

- role=tablist, tab et tabpanel bien répartis
- Flèches gauche/droite pour circuler entre onglets
- Un seul onglet dans l'ordre de tabulation à la fois

Le catalogue des plugins d'onglets pour WordPress est immense, mais leur qualité d'accessibilité est rarement au rendez-vous : beaucoup reposent sur des `<div>` cliquables sans gestion de touche, ou sur des bibliothèques JavaScript figées qui ne suivent pas le motif ARIA officiel. Pour un client qui souhaitait des onglets sur une page de présentation de services, nous avons choisi de construire un bloc personnalisé plutôt que d'installer une dépendance supplémentaire — moins de code tiers à maintenir, et un contrôle total sur l'accessibilité du résultat.

Le bloc s'enregistre comme n'importe quel bloc personnalisé, via `register_block_type()` côté PHP et un script d'édition en JavaScript. C'est le rendu final en façade — la partie visible par les visiteurs du site, générée par la fonction de rendu du bloc — qui nous intéresse ici, puisque c'est elle qui doit respecter le motif ARIA *tabs*.

## La structure HTML du motif tabs

Le motif ARIA officiel distingue trois rôles : `tablist` pour le conteneur des boutons d'onglets, `tab` pour chaque bouton, et `tabpanel` pour chaque panneau de contenu associé :

```
<div class="onglets-services">
  <div role="tablist" aria-label="Nos services">
    <button role="tab" id="tab-conseil" aria-selected="true" aria-controls="panel-conseil">Conseil</button>
    <button role="tab" id="tab-dev" aria-selected="false" aria-controls="panel-dev" tabindex="-1">Développement</button>
    <button role="tab" id="tab-maintenance" aria-selected="false" aria-controls="panel-maintenance" tabindex="-1">Maintenance</button>
  </div>
  <div role="tabpanel" id="panel-conseil" aria-labelledby="tab-conseil">
    <p>Contenu de l'onglet Conseil…</p>
  </div>
  <div role="tabpanel" id="panel-dev" aria-labelledby="tab-dev" hidden>
    <p>Contenu de l'onglet Développement…</p>
  </div>
  <div role="tabpanel" id="panel-maintenance" aria-labelledby="tab-maintenance" hidden>
    <p>Contenu de l'onglet Maintenance…</p>
  </div>
</div>
```

## Le détail qui change tout : un seul tab dans l'ordre de tabulation

> L'essentiel à retenir : role=tablist, tab et tabpanel bien répartis ; Flèches gauche/droite pour circuler entre onglets ; Un seul onglet dans l'ordre de tabulation à la fois

Remarquez que seul le premier bouton d'onglet (celui sélectionné) porte un `tabindex` implicite normal, tandis que les autres reçoivent `tabindex="-1"`. C'est volontaire, et c'est la partie la plus souvent ratée dans les implémentations maison : un appui sur Tab ne doit faire circuler l'utilisateur qu'entre un onglet actif et l'élément suivant de la page, jamais à travers chaque bouton d'onglet un par un. Le déplacement entre onglets se fait uniquement avec les flèches gauche et droite, une fois le focus posé sur le groupe.

## Le script de navigation par flèches

```
document.querySelectorAll('[role="tablist"]').forEach((liste) => {
  const onglets = Array.from(liste.querySelectorAll('[role="tab"]'));

  onglets.forEach((onglet, index) => {
    onglet.addEventListener('click', () => activerOnglet(onglets, onglet));
    onglet.addEventListener('keydown', (event) => {
      if (event.key === 'ArrowRight') {
        activerOnglet(onglets, onglets[index + 1] || onglets[0]);
      }
      if (event.key === 'ArrowLeft') {
        activerOnglet(onglets, onglets[index - 1] || onglets[onglets.length - 1]);
      }
    });
  });
});

function activerOnglet(onglets, cible) {
  onglets.forEach((onglet) => {
    const actif = onglet === cible;
    onglet.setAttribute('aria-selected', String(actif));
    onglet.setAttribute('tabindex', actif ? '0' : '-1');
    document.getElementById(onglet.getAttribute('aria-controls')).hidden = !actif;
  });
  cible.focus();
}
```

## Enregistrer le bloc côté PHP

La fonction de rendu du bloc génère exactement ce HTML à partir des attributs saisis par l'utilisateur dans l'éditeur (titres d'onglets et contenu de chaque panneau, stockés comme un tableau d'objets dans les attributs du bloc) :

```
register_block_type( __DIR__ . '/build', array(
  'render_callback' => 'agence_rendu_bloc_onglets',
) );
```

## Ce que ce choix a évité

- Une dépendance supplémentaire à maintenir et à tenir à jour à chaque montée de version de WordPress.
- Un risque de conflit CSS avec un plugin d'onglets tiers qui charge sa propre feuille de style non maîtrisée.
- Une accessibilité incertaine, la plupart des plugins d'onglets grand public n'appliquant pas rigoureusement le motif ARIA complet.

## Pour aller plus loin

Le motif complet, avec ses variantes d'orientation verticale et horizontale, est décrit dans le [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) du W3C — la référence à suivre au pied de la lettre avant d'écrire tout composant à onglets, plugin ou bloc personnalisé.
