Composant · Données et contenus riches

Carrousel accessible : bouton pause, critère 13.8 et ARIA

Rendre un carrousel conforme RGAA : bouton pause du défilement automatique (critère 13.8), pause au survol et au focus, diapositives masquées avec hidden, aria-roledescription.

Le carrousel est une région qui présente plusieurs contenus en séquence au même emplacement, généralement une diapositive à la fois, avec des contrôles précédent et suivant et, parfois, un défilement automatique. Il n'existe aucun élément HTML natif. La façon la plus simple et la plus sûre de rendre un carrousel conforme tient en une phrase : ne le faites pas défiler automatiquement. La rotation automatique apporte peu et coûte cher : un bouton pause visible, la mise en pause au survol ET au focus, la gestion d'une zone aria-live et un chemin prefers-reduced-motion. Sans elle, le critère 13.8 est satisfait par construction et il ne reste presque rien à casser.

Quand ce motif est le mauvais choix

Le carrousel se justifie quand plusieurs blocs de contenu doivent réellement partager le même emplacement à l'écran. Si l'objectif est « faire tenir cinq messages sur la page d'accueil », une liste statique de cartes est presque toujours plus efficace et plus accessible : les taux de clic des carrousels sont notoirement faibles, et la quasi-totalité des clics vont à la première diapositive.

Si le carrousel est retenu malgré tout :

  • préférez la variante sans défilement automatique, contrôlée uniquement par les boutons précédent et suivant ;
  • la variante à pastilles de sélection en role="tab" n'est valide que si les pastilles se comportent réellement comme des onglets (navigation aux flèches, panneau associé) ; des pastilles décoratives n'ont pas à porter ce rôle.

Pour le débat complet sur l'opportunité même du composant et ses alternatives : Carrousels accessibles : faut-il vraiment les utiliser ?

Critères RGAA applicables

Interaction clavier attendue

ToucheAction
Tab / Maj + TabParcourt les éléments interactifs : le bouton pause d'abord (premier dans le DOM), puis les contenus de la diapositive visible, puis précédent et suivant.
Entrée / Espace (bouton pause)Arrête ou relance le défilement automatique.
Entrée / Espace (précédent / suivant)Affiche la diapositive précédente ou suivante.

Règle comportementale en plus des touches : l'arrivée du focus dans le carrousel doit suspendre le défilement automatique. Sans cela, l'utilisateur clavier tabule vers un lien de la diapositive 2 et le carrousel avance, déplaçant le lien sous ses doigts.

Rôles et attributs ARIA

Le conteneur est une région nommée : aria-roledescription="carrousel" en change l'annonce mais ne constitue PAS un nom, un aria-label reste obligatoire. Chaque diapositive porte role="group", aria-roledescription="diapositive" et un nom donnant la position (« 1 sur 3 »). Si le carrousel défile automatiquement, le bouton de pause est visible et placé AVANT les diapositives dans le DOM, pour que l'utilisateur puisse arrêter le mouvement avant de tabuler dedans.

<section id="carrousel" aria-roledescription="carrousel" aria-label="Actualités">
  <!-- Le bouton pause vient EN PREMIER dans le DOM (critère 13.8). -->
  <button type="button" id="carrousel-pause">
    <svg aria-hidden="true" focusable="false"><use href="#icone-pause"/></svg>
    <span class="sr-only">Mettre en pause le défilement automatique</span>
  </button>

  <!-- aria-live est géré en JS : "off" pendant la rotation, "polite" à l'arrêt. -->
  <div id="carrousel-diapos" aria-live="off">
    <div role="group" aria-roledescription="diapositive" aria-label="1 sur 3">
      <img src="actu-1.jpg" alt="">
      <h3><a href="/actualites/1">Publication du schéma pluriannuel</a></h3>
    </div>
    <div role="group" aria-roledescription="diapositive" aria-label="2 sur 3" hidden>…</div>
    <div role="group" aria-roledescription="diapositive" aria-label="3 sur 3" hidden>…</div>
  </div>

  <button type="button" id="carrousel-prec"><span class="sr-only">Diapositive précédente</span></button>
  <button type="button" id="carrousel-suiv"><span class="sr-only">Diapositive suivante</span></button>
</section>
/* Les contrôles se posent sur des photos : un liseré d'une seule couleur peut
   disparaître sur une image claire. Le double liseré garantit le contraste
   sur n'importe quel fond (critère 10.7). */
#carrousel button:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 5px #000;
}
const carrousel = document.getElementById('carrousel');
const conteneur = document.getElementById('carrousel-diapos');
const diapos = [...carrousel.querySelectorAll('[role="group"]')];
const pauseBtn = document.getElementById('carrousel-pause');
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
let index = 0;
let timer = null;

function afficher(i) {
  index = (i + diapos.length) % diapos.length;
  // `hidden`, pas une translation : les diapositives hors écran doivent sortir
  // de l'arbre d'accessibilité ET de l'ordre de tabulation (critère 10.8).
  diapos.forEach((diapo, n) => { diapo.hidden = n !== index; });
}

function lancer() {
  if (reduceMotion.matches) return; // jamais de rotation sous reduced-motion
  timer = setInterval(() => afficher(index + 1), 5000);
  // En rotation : annoncer chaque diapositive serait incessant.
  conteneur.setAttribute('aria-live', 'off');
  pauseBtn.querySelector('.sr-only').textContent = 'Mettre en pause le défilement automatique';
}

function arreter() {
  clearInterval(timer);
  timer = null;
  // À l'arrêt, les changements sont voulus par l'utilisateur : les annoncer est utile.
  conteneur.setAttribute('aria-live', 'polite');
  pauseBtn.querySelector('.sr-only').textContent = 'Relancer le défilement automatique';
}

pauseBtn.addEventListener('click', () => (timer ? arreter() : lancer()));

// Une action explicite (précédent / suivant) arrête la rotation pour la session.
document.getElementById('carrousel-suiv').addEventListener('click', () => { arreter(); afficher(index + 1); });
document.getElementById('carrousel-prec').addEventListener('click', () => { arreter(); afficher(index - 1); });

// Pause au survol ET au focus : un utilisateur clavier ne survole jamais.
carrousel.addEventListener('mouseenter', () => timer && arreter());
carrousel.addEventListener('focusin', () => timer && arreter());

À ne pas faire :

<!-- Rotation automatique sans contrôle de pause : non-conformité directe
     au critère 13.8, pas un simple avertissement. -->
<div class="carrousel" data-autoplay="5000">…</div>

<!-- aria-live="polite" sur un conteneur en rotation : une diapositive est
     annoncée toutes les 5 secondes, indéfiniment, par-dessus ce que
     l'utilisateur lisait ailleurs. -->
<div class="carrousel-diapos" aria-live="polite">…</div>

<!-- aria-roledescription sans nom accessible : annoncé « carrousel » sans
     dire lequel. roledescription RENOMME le rôle, il ne nomme pas la région. -->
<section aria-roledescription="carrousel">…</section>
/* À ne pas faire : masquer les diapositives par translation. Les trois
   diapositives restent dans l'arbre d'accessibilité et dans l'ordre de
   tabulation : l'utilisateur tabule dans des liens invisibles (critère 10.8). */
.diapo { transform: translateX(100%); }
.diapo--active { transform: translateX(0); }

Sous prefers-reduced-motion: reduce, ne pas défiler automatiquement du tout, pas seulement ralentir. Et une action explicite de l'utilisateur (clic sur suivant) doit arrêter la rotation pour la session, jamais la relancer.

Défauts fréquents et impact utilisateur

  • Défilement automatique sans bouton de pause : non-conformité directe au critère 13.8. Toute personne qui lit lentement, ou dont le lecteur d'écran est au milieu d'une phrase, perd le contenu.
  • Pause au survol seulement, pas au focus : un utilisateur clavier ne survole jamais ; il tabule vers un lien de la diapositive 2 et le carrousel avance, déplaçant le lien sous ses doigts.
  • Diapositives masquées par translation CSS : tous les liens de toutes les diapositives restent tabulables en même temps ; l'utilisateur tabule dans du contenu invisible (critère 10.8). C'est le comportement par défaut de la plupart des bibliothèques de carrousel : vérifiez avant d'en adopter une.
  • aria-live="polite" actif pendant la rotation : une nouvelle diapositive est annoncée en boucle, en interrompant ce que l'utilisateur lisait ailleurs sur la page.
  • Contrôles en icônes sans nom accessible : annoncés « bouton, bouton, bouton » ; pastilles sans libellé qui n'indiquent ni la position ni la destination.
  • Rotation maintenue malgré prefers-reduced-motion: reduce : pour une personne avec un trouble vestibulaire, une transition non sollicitée toutes les quelques secondes peut provoquer de réelles nausées.

Ce que les outils automatiques ne détectent pas

Un scanner automatique, le nôtre compris, repère les boutons en icône sans nom accessible et certains attributs ARIA incohérents. Mais l'essentiel du critère 13.8 lui échappe : la présence d'un défilement automatique, l'absence de bouton de pause, la pause au survol sans pause au focus. Restent également manuels :

  • les diapositives masquées par translation : seul un parcours à Tab révèle qu'on atteint des liens invisibles ;
  • le comportement sous prefers-reduced-motion: reduce (aucune rotation attendue) ;
  • le silence du lecteur d'écran pendant la rotation, et l'annonce des changements une fois la rotation arrêtée ;
  • la rotation qui se suspend réellement quand le focus entre dans le carrousel.

Ce motif se vérifie presque entièrement à la main.

Vérifier ce composant

Protocole manuel, dans cet ordre :

  1. Le test du critère 13.8 d'abord : le carrousel défile-t-il automatiquement ? Si oui, existe-t-il un contrôle de pause visible, atteignable au clavier, qui l'arrête réellement ? Un non est une non-conformité, pas un avertissement.
  2. Le test du contenu qui se dérobe : lancez la rotation, puis tabulez vers un lien de la diapositive visible. La rotation doit se suspendre. Si la diapositive change pendant que vous tabulez, le lien se déplace sous vos doigts.
  3. Le test de l'ordre de tabulation : traversez tout le carrousel à Tab. Vous ne devez atteindre que les liens de la diapositive visible. Si vous passez sur des liens de diapositives invisibles, elles sont masquées par translation (critère 10.8).
  4. Reduced motion : activez prefers-reduced-motion: reduce. Le carrousel ne doit plus tourner du tout.
  5. Au lecteur d'écran : silence pendant la rotation ; annonce des changements de diapositive une fois la rotation arrêtée. Une annonce spontanée toutes les 5 secondes signale un aria-live actif pendant la rotation.

Deux outils gratuits du site pour appuyer la vérification : le testeur de focus visible valide vos styles :focus sur des contrôles posés sur des visuels chargés (critère 10.7), et le simulateur de lecteur d'écran affiche l'arbre d'accessibilité : vous y verrez immédiatement si les diapositives masquées en sortent réellement et si chaque bouton a un nom.

Approfondir avec les fiches critères

Vérifier ce composant sur votre site ?

Le scan repère les défauts détectables automatiquement (rôles incohérents, champs sans étiquette, attributs ARIA orphelins) ; le reste se vérifie à la main avec les protocoles de cette fiche.

Lancer un scan gratuit

Toutes les fiches composants accessibles