WCAG A Prestataire Complexe

Les composants interactifs de votre site — menus déroulants, accordéons, onglets — fonctionnent-ils correctement avec les logiciels de lecture ?

Critère officiel 7.1 — Chaque script est-il, si nécessaire, compatible avec les technologies d’assistance ?

Pourquoi c'est important

Une personne aveugle navigue dans un accordéon FAQ ou un menu déroulant sans voir l'état ouvert ou fermé ni les options disponibles. Si le développeur n'a pas déclaré ces états dans le code, le logiciel de lecture annonce uniquement « bouton » sans préciser s'il est développé ou réduit, ni quel contenu il contient.

Exemples concrets

Ce qui est conforme

L'accordéon FAQ annonce correctement : « Bouton : Comment inscrire mon enfant à la cantine ? — Réduit. Appuyez sur Entrée pour développer. » Après activation : « Développé. » Le contenu est lu. L'état du composant est toujours communiqué.

Ce qui pose problème

Le même accordéon annonce seulement : « Bouton. Bouton. Bouton. » Le logiciel de lecture ne signale pas que ces boutons ouvrent du contenu caché, ni quel contenu ils contrôlent.

Comment agir

Lors de tout ajout de composant interactif (menu, accordéon, carrousel, onglets), incluez dans le cahier des charges : « Les composants interactifs doivent implémenter les attributs ARIA d'état (aria-expanded, aria-selected, aria-haspopup) conformément au RGAA 4.1. » Test rapide : appuyez sur Tab pour atteindre le composant, puis Entrée — s'il ne réagit pas, le problème est confirmé.

Règles clés

  • Règle 1 de l'ARIA : utiliser l'élément HTML natif en premier. <button> avant role="button", <a> avant role="link".
  • Tout élément interactif custom doit avoir : un rôle ARIA (role), un nom accessible (aria-label ou contenu texte), et un état si pertinent (aria-expanded, aria-selected, aria-checked…).
  • aria-controls doit référencer un id existant dans le même document — sinon il vaut mieux l'omettre.
  • Le pattern Disclosure (accordéon, menu déroulant de navigation) est différent du pattern Menu/Menubar — ne pas confondre.
  • Une modal doit piéger le focus (Tab/Shift+Tab circulent dans la modal uniquement) et se fermer avec Échap.

Erreurs fréquentes

  • Déclencheur de menu ou d'accordéon sur une <div> ou <span> sans role="button"
  • aria-expanded absent ou non mis à jour à l'ouverture/fermeture
  • aria-controls renseigné mais référençant un id inexistant dans le DOM
  • role="menu" utilisé pour une navigation de site (à éviter — utiliser le pattern Disclosure Navigation à la place)
  • Focus non géré à l'ouverture d'une modal : le focus reste sur le déclencheur au lieu de passer à l'intérieur
  • Focus non rendu à l'élément déclencheur à la fermeture d'une modal
  • Absence de piège de focus (focus trap) dans une modal ouverte
  • Composant uniquement activable au clic — pas de support Enter/Espace sur les éléments role="button"
  • aria-haspopup utilisé sur un bouton contrôlant une liste de liens — la valeur doit correspondre au rôle réel du conteneur (menu, listbox, tree, grid, dialog). Pour une sous-navigation, utiliser aria-expanded à la place
  • aria-haspopup='true' sur un bouton de navigation — la valeur 'true' équivaut à 'menu' par défaut, créant les mêmes problèmes que aria-haspopup='menu'
  • Confusion navigation/menu : utiliser aria-haspopup uniquement pour de vrais menus d'actions (comme les menus d'application desktop), pas pour des listes de liens de navigation
  • aria-labelledby ou aria-describedby référençant un ID qui n'existe pas dans le DOM, l'attribut devient inopérant et l'élément se rabat sur son nom accessible par défaut | aria-labelledby avec références multiples ('btn1 btn2') où une seule référence existe. Seul le texte de l'élément existant est concaténé, l'ID manquant est ignoré silencieusement | aria-labelledby référençant un élément masqué par aria-hidden='true' — fonctionne correctement, le texte est extrait malgré le masquage pour les technologies d'assistance (AT)
  • Overlay ou script de remédiation automatique (type UserWay, AccessiBe) ajoutant aria-expanded ou aria-label via JavaScript externe sur un composant React/Vue/Angular : les attributs ARIA injectés après rendu sont effacés à chaque re-rendu du framework — l'état ARIA redevient incorrect de façon silencieuse et intermittente, sans erreur visible dans l'outil de test

Exemples de code

accordéon — déclencheur non sémantique

✗ Non conforme
<div class="accordion-trigger" onclick="toggle()">FAQ : Comment commander ?</div>
<div id="panel-1" style="display:none">...</div>

Une <div> cliquable n'est pas focusable au clavier et n'a aucun rôle. Le lecteur d'écran ne sait pas que c'est un déclencheur interactif.

accordéon — déclencheur accessible

✓ Conforme
<button
  type="button"
  aria-expanded="false"
  aria-controls="panel-1"
>FAQ : Comment commander ?</button>
<div id="panel-1" hidden>...</div>

<button> est focusable et activable au clavier nativement. aria-expanded annonce l'état ouvert/fermé. aria-controls lie le bouton au panneau. hidden masque le contenu aux AT quand il est fermé.

navigation avec sous-menus — pattern incorrect

✗ Non conforme
<nav>
  <ul role="menubar">
    <li role="menuitem">Produits
      <ul role="menu">
        <li role="menuitem"><a href="/logiciels">Logiciels</a></li>
      </ul>
    </li>
  </ul>
</nav>

role="menu" et role="menuitem" impliquent des comportements clavier spécifiques aux applications desktop (flèches directionnelles). Les lecteurs d'écran passent en mode application, ce qui désactive la navigation par lecture. À proscrire pour une navigation de site.

navigation avec sous-menus — pattern Disclosure

✓ Conforme
<nav aria-label="Navigation principale">
  <ul>
    <li>
      <a href="/produits">Produits</a>
      <button
        type="button"
        aria-expanded="false"
        aria-controls="sous-menu-produits"
        aria-label="Sous-menu Produits"
      >▾</button>
      <ul id="sous-menu-produits" hidden>
        <li><a href="/logiciels">Logiciels</a></li>
      </ul>
    </li>
  </ul>
</nav>

Pattern Disclosure Navigation (APG recommandé). Pas de role="menu" — navigation standard par Tab. Le bouton séparé gère l'ouverture du sous-menu sans quitter le lien parent.

modal — gestion du focus

✗ Non conforme
// À l'ouverture : rien
document.getElementById('modal').style.display = 'block';

Le focus reste sur le déclencheur. L'utilisateur de lecteur d'écran ne sait pas que la modal est ouverte, et ne peut pas interagir avec son contenu.

modal — gestion du focus

✓ Conforme
function ouvrirModal(triggerEl, modalEl) {
  modalEl.removeAttribute('hidden');
  // Déplacer le focus sur le premier élément focusable
  const premierFocusable = modalEl.querySelector(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  premierFocusable?.focus();
  // Mémoriser le déclencheur pour restaurer le focus
  modalEl._trigger = triggerEl;
}

function fermerModal(modalEl) {
  modalEl.setAttribute('hidden', '');
  // Rendre le focus au déclencheur
  modalEl._trigger?.focus();
}

À l'ouverture, le focus passe au premier élément focusable de la modal. À la fermeture, il retourne sur le déclencheur — l'utilisateur reprend là où il était.

Référence WCAG : 2.5.3, 4.1.2

La lettre de l'Atelier A11Y

Ressources pédagogiques, critères RGAA commentés et retours de terrain : une lettre mensuelle pour progresser sur l'accessibilité numérique, sans jargon.

  • Nouveaux articles et ressources pédagogiques
  • Critères RGAA décortiqués avec des exemples concrets
  • Bonnes pratiques et retours d'expérience terrain
S'abonner à la newsletter (s'ouvre dans un nouvel onglet)

Gratuit. Désabonnement possible à tout moment.