Un client du secteur de la formation professionnelle demandait un bloc « Onglets » pour présenter le contenu de ses programmes de formation, une section par onglet (objectifs, prérequis, tarifs, débouchés). La contrainte imposée par son propre cahier des charges accessibilité interdisait toute bibliothèque JavaScript tierce non auditée, ce qui excluait les solutions habituelles basées sur des composants npm généralistes. Le résultat attendu devait pourtant respecter les mêmes exigences qu’un composant à onglets professionnel : gestion du focus au clavier, attributs ARIA corrects, annonce cohérente par un lecteur d’écran.
L’Interactivity API, combinée aux rôles et attributs ARIA du motif « Tabs » documenté par les pratiques d’accessibilité du web, a permis de construire ce composant intégralement en JavaScript natif WordPress, sans dépendance externe, tout en respectant les attentes d’un utilisateur naviguant exclusivement au clavier.
La structure sémantique attendue
Le motif Tabs repose sur trois rôles ARIA distincts : tablist pour le conteneur des boutons d’onglet, tab pour chaque bouton individuel, et tabpanel pour chaque zone de contenu associée. Chaque onglet référence le panneau qu’il contrôle via aria-controls, et chaque panneau référence l’onglet qui le décrit via aria-labelledby.
<div data-wp-interactive="mon-agence/onglets">
<div role="tablist">
<button role="tab" id="tab-objectifs" aria-controls="panel-objectifs"
data-wp-bind--aria-selected="state.estActif::objectifs"
data-wp-on--click="actions.selectionner"
data-wp-context='{"cle": "objectifs"}'>Objectifs</button>
<button role="tab" id="tab-tarifs" aria-controls="panel-tarifs"
data-wp-bind--aria-selected="state.estActif::tarifs"
data-wp-on--click="actions.selectionner"
data-wp-context='{"cle": "tarifs"}'>Tarifs</button>
</div>
<div role="tabpanel" id="panel-objectifs" aria-labelledby="tab-objectifs"
data-wp-bind--hidden="!state.estActif::objectifs">
<p>Contenu des objectifs</p>
</div>
</div>
Le store et la logique de sélection

import { store, getContext } from '@wordpress/interactivity';
store( 'mon-agence/onglets', {
state: {
ongletActif: 'objectifs',
},
actions: {
selectionner() {
const { cle } = getContext();
store( 'mon-agence/onglets' ).state.ongletActif = cle;
},
},
} );
Chaque bouton porte un contexte local différent via data-wp-context, ce qui permet à une seule action selectionner partagée de savoir quel onglet a réellement été cliqué, sans dupliquer la logique pour chaque onglet individuellement.
La navigation au clavier par flèches
Le motif Tabs attendu par les technologies d’assistance impose que les flèches gauche et droite déplacent le focus entre les onglets, sans nécessiter de tabulation répétée. Cette exigence, souvent négligée dans les implémentations improvisées, s’ajoute directement dans l’action de gestion du clavier.
actions: {
naviguerAuClavier( event ) {
if ( ! [ 'ArrowLeft', 'ArrowRight' ].includes( event.key ) ) return;
const onglets = Array.from(
event.target.closest( '[role="tablist"]' ).querySelectorAll( '[role="tab"]' )
);
const index = onglets.indexOf( event.target );
const suivant = event.key === 'ArrowRight'
? ( index + 1 ) % onglets.length
: ( index - 1 + onglets.length ) % onglets.length;
onglets[ suivant ].focus();
},
},
Une checklist de conformité avant livraison
- Chaque bouton d’onglet possède-t-il un aria-controls pointant vers un panneau existant ?
- L’attribut aria-selected reflète-t-il correctement l’onglet actif à tout moment ?
- Les flèches gauche et droite déplacent-elles le focus sans activer automatiquement l’onglet visé ?
- Un panneau masqué utilise-t-il l’attribut hidden plutôt qu’un simple display:none en CSS non synchronisé ?
Tester avec un vrai lecteur d’écran
Aucune checklist ne remplace un test avec un lecteur d’écran réel : sur ce projet, un test avec NVDA a révélé que l’annonce du changement d’onglet restait silencieuse tant que le focus ne se déplaçait pas explicitement vers le panneau ou son contenu après sélection, un détail invisible en lecture de code mais immédiatement perceptible à l’écoute.
Un composant à onglets qui fonctionne visuellement à la souris ne prouve rien sur son accessibilité réelle. Seul un parcours clavier complet, du premier au dernier onglet, révèle si le motif ARIA a été correctement implémenté.
En résumé
Ce projet démontre qu’un composant interactif accessible et conforme reste tout à fait accessible sans bibliothèque tierce, à condition de traiter la sémantique ARIA et la navigation clavier comme des exigences de premier ordre, au même titre que l’apparence visuelle. L’Interactivity API fournit les briques nécessaires ; le respect scrupuleux du motif Tabs documenté reste, lui, entièrement à la charge du développeur.