« ARIA hidden element must not be focusable ». Ce message d’erreur, remonté par axe-core lors de l’intégration continue d’un site vitrine de coopérative agricole, désignait le carrousel d’avis de coopérateurs affiché sur la page d’accueil. Cinq témoignages défilaient automatiquement, un seul visible à la fois, les quatre autres masqués visuellement par un overflow: hidden sur le conteneur. Le problème : ces quatre slides masquées restaient pleinement accessibles au clavier, chacune contenant un lien « Lire l’avis complet » qui recevait le focus normalement.
Symptôme : un focus qui disparaît visuellement
Le test manuel au clavier révélait le symptôme sans ambiguïté : en appuyant sur Tab depuis le premier lien visible du carrousel, le focus suivant sautait vers un lien invisible, quelque part hors de l’écran. Aucune indication visuelle n’accompagnait ce focus, ce qui désoriente autant une personne voyante naviguant au clavier qu’une personne malvoyante utilisant un agrandissement d’écran.
Le code du carrousel avant correction ressemblait à ceci :
<div class="carrousel-avis">
<div class="slide" aria-hidden="false">
<p>« Coopérative sérieuse, accompagnement au top. »</p>
<a href="/avis/1">Lire l'avis complet</a>
</div>
<div class="slide" aria-hidden="true">
<p>« Les prix de reprise sont transparents. »</p>
<a href="/avis/2">Lire l'avis complet</a>
</div>
<!-- 3 slides supplémentaires, même structure -->
</div>
L’attribut aria-hidden="true" était bien posé sur les slides masquées, ce qui aurait dû suffire à les retirer de l’arbre d’accessibilité. Mais la règle WAI-ARIA est stricte sur ce point précis : un élément portant aria-hidden="true" ne doit contenir aucun élément focusable, faute de quoi le comportement devient incohérent selon les navigateurs et les technologies d’assistance, d’où l’erreur remontée par axe-core.
Diagnostic : deux attributs qui doivent évoluer ensemble

Le lien « Lire l’avis complet » restait un élément nativement focusable (une balise <a href>), et rien dans le code ne désactivait cette possibilité de focus au moment où la slide passait à l’état masqué. Les deux états — visuellement masqué, focusable au clavier — évoluaient de façon indépendante, alors qu’ils doivent impérativement rester synchronisés.
Correctif : synchroniser aria-hidden et tabindex par script
La correction ajoute une gestion explicite de l’attribut tabindex à chaque changement de slide active, appliquée à tous les éléments focusables d’une slide masquée :
function synchroniserSlides() {
document.querySelectorAll('.slide').forEach((slide) => {
const masquee = slide.getAttribute('aria-hidden') === 'true';
slide.querySelectorAll('a, button').forEach((element) => {
element.setAttribute('tabindex', masquee ? '-1' : '0');
});
});
}
Cette fonction est appelée à chaque rotation automatique du carrousel, juste après la mise à jour des attributs aria-hidden sur chaque slide. Le résultat : un lien masqué visuellement devient également absent de l’ordre de tabulation, exactement comme le prescrit la spécification.
Prévention : un test automatisé dans la chaîne de déploiement
Pour éviter qu’un futur composant de type carrousel reproduise la même erreur, un test avec Playwright et la bibliothèque axe-core a été ajouté à la chaîne de déploiement, exécuté sur la page d’accueil à chaque mise en production :
const { test, expect } = require('@playwright/test');
const AxeBuilder = require('@axe-core/playwright').default;
test('carrousel avis sans slide masquee focusable', async ({ page }) => {
await page.goto('/');
const resultats = await new AxeBuilder({ page })
.include('.carrousel-avis')
.analyze();
expect(resultats.violations).toEqual([]);
});
Ce test échoue désormais automatiquement si un nouveau composant de carrousel reproduit l’erreur, bloquant la mise en production avant qu’elle n’atteigne les visiteurs du site.
Un attribut
aria-hiddenposé sur un conteneur ne protège jamais, à lui seul, les éléments focusables qu’il contient : la synchronisation avectabindexdoit être gérée explicitement, à chaque changement d’état.
Notre verdict
L’erreur « ARIA hidden element must not be focusable » désigne presque toujours le même défaut : un état visuel masqué qui n’a pas été répercuté sur la possibilité de recevoir le focus. La correction reste simple une fois le diagnostic posé, mais elle mérite un test automatisé durable, car ce type de composant tend à être copié-collé d’un projet à l’autre sans revérification systématique.