Comment annoncer à une personne malvoyante quel service du menu — entrée, plat ou dessert — est actuellement affiché, sans dupliquer l’information trois fois dans le code ? C’est la question posée par la refonte de la page de menu d’un restaurant gastronomique qui souhaitait remplacer ses onglets visuels par quelque chose de plus lisible, sans repartir d’une extension tierce.
L’indicateur d’étape visuel — trois pastilles reliées par un trait, la pastille active mise en évidence — est un classique du web. Son défaut le plus courant : toute l’information repose sur la couleur et la position, deux signaux invisibles pour un lecteur d’écran. La solution ne demande ni bibliothèque JavaScript ni extension : une liste ordonnée, un attribut aria-current, et une structure de titres cohérente suffisent.
Étape 1 : poser la structure sémantique
Avant tout style, l’indicateur doit exister comme une liste ordonnée classique. Cela garantit qu’un lecteur d’écran annonce « liste de 3 éléments » et permet de se déplacer d’un élément à l’autre avec les raccourcis de navigation par liste :
<nav aria-label="Étapes du menu">
<ol class="menu-etapes">
<li><a href="#entree">Entrée</a></li>
<li><a href="#plat" aria-current="step">Plat</a></li>
<li><a href="#dessert">Dessert</a></li>
</ol>
</nav>
La valeur step de l’attribut aria-current est justement prévue par la spécification WAI-ARIA pour ce cas d’usage : elle indique qu’un élément représente l’étape courante dans un processus séquentiel, à distinguer de aria-current="page" réservé à la navigation entre pages.
Étape 2 : synchroniser le style sur l’état, pas l’inverse

L’erreur fréquente consiste à poser une classe CSS pour l’effet visuel, puis à oublier l’attribut ARIA correspondant. Il faut inverser l’ordre de dépendance : c’est l’attribut aria-current qui pilote le style, jamais l’inverse.
.menu-etapes a[aria-current="step"] {
font-weight: 700;
text-decoration: underline;
text-underline-offset: 4px;
}
Ce sélecteur CSS garantit qu’il est impossible d’avoir un style « actif » sans l’annonce correspondante pour les technologies d’assistance : les deux sont techniquement liés par construction.
Étape 3 : gérer le changement d’étape au clic ou au défilement
Si le menu se découpe en sections d’une seule page avec défilement, un petit script suffit pour déplacer l’attribut aria-current au bon endroit lors du changement de section visible :
const sections = document.querySelectorAll('.menu-etapes a');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entree) => {
if (entree.isIntersecting) {
const cible = document.querySelector(
`.menu-etapes a[href="#${entree.target.id}"]`
);
sections.forEach((lien) => lien.removeAttribute('aria-current'));
if (cible) cible.setAttribute('aria-current', 'step');
}
});
}, { threshold: 0.6 });
document.querySelectorAll('main section[id]').forEach((s) => observer.observe(s));
L’utilisation d’un IntersectionObserver évite d’écouter l’évènement de défilement directement, ce qui économise des cycles de calcul et ne modifie l’attribut qu’au moment pertinent.
Étape 4 : vérifier avec un lecteur d’écran
Le test se fait en trois minutes avec NVDA ou VoiceOver : on active la navigation par liste, on écoute l’annonce de chaque lien. Le résultat attendu ressemble à « Plat, lien, étape courante » plutôt qu’un simple « Plat, lien ». Si l’annonce ne mentionne pas l’étape courante, l’attribut n’est probablement pas positionné au bon moment du cycle de vie du composant.
- Vérifier que chaque lien reste atteignable au clavier avec
Tab; - Vérifier que l’ordre de tabulation suit l’ordre visuel entrée, plat, dessert ;
- Vérifier qu’un seul élément à la fois porte
aria-current="step".
En résumé
Un indicateur d’étape accessible ne demande ni animation compliquée ni bibliothèque externe. La combinaison d’une liste ordonnée, de aria-current="step" et d’un script minimal pour synchroniser l’état suffit à rendre l’information disponible à toutes les personnes qui consultent le menu, quel que soit leur mode de navigation.