Composant · Formulaires et saisie

Interrupteur accessible (role switch) : critères RGAA et ARIA

Interrupteur (switch) accessible : role switch et aria-checked, différence avec la case à cocher, état non porté par la couleur seule, critères RGAA.

Un interrupteur (switch) bascule entre deux états mutuellement exclusifs, activé et désactivé, avec effet immédiat. Il doit être construit sur un contrôle réel : <button role="switch" aria-checked> pour un réglage qui s'applique instantanément, ou <input type="checkbox" role="switch"> quand la valeur doit être soumise avec un formulaire, car seul un vrai champ transmet une valeur. Dans les deux cas, le focus, la touche Espace et le pointeur fonctionnent nativement, sans script de compensation.

Quand ce motif est le mauvais choix

La distinction avec la case à cocher est le point clé du motif :

  • Le changement ne prend effet qu'à la validation du formulaire (bouton « Enregistrer ») : c'est une case à cocher, même si la maquette dessine un curseur glissant.
  • Un troisième état est nécessaire (partiellement activé) : c'est une case à cocher tri-état. Un interrupteur est strictement binaire, aria-checked="mixed" y est invalide.
  • Le contrôle exécute une action ponctuelle plutôt qu'il ne maintient un état : c'est un bouton, éventuellement un bouton bascule avec aria-pressed.
InterrupteurCase à cocher
Prise d'effetImmédiateÀ la soumission du formulaire
États possibles2 (jamais « mixed »)3 (« mixed » autorisé)
Annonce au lecteur d'écran« interrupteur, activé / désactivé »« case à cocher, cochée / non cochée »
Valeur de formulaireVia <input type="checkbox" role="switch">Native

Critères RGAA applicables

Interaction clavier attendue

ToucheAction
EspaceBascule l'état de l'interrupteur : c'est la touche qui doit impérativement fonctionner
EntréeBascule l'état (optionnel pour ce motif)

Rôles et attributs ARIA

role="switch" identifie l'élément, aria-checked (true ou false, jamais mixed) porte l'état, et un nom accessible est requis (aria-labelledby, aria-label ou contenu texte). Le nom désigne le réglage (« Notifications par e-mail »), jamais l'état : c'est aria-checked qui dit activé ou désactivé.

<!-- Le nom accessible vient d'aria-labelledby : le texte visible EST
     le nom du contrôle. Le visuel du rail est purement décoratif. -->
<div class="ligne-interrupteur">
  <span id="notif-label">Notifications par e-mail</span>
  <button type="button" role="switch" aria-checked="false"
          aria-labelledby="notif-label">
    <span class="interrupteur__rail" aria-hidden="true">
      <span class="interrupteur__curseur"></span>
    </span>
  </button>
</div>
const interrupteur = document.querySelector('[role="switch"]');

// Pas de gestionnaire keydown : un vrai <button> déclenche déjà
// l'événement click à l'appui sur Espace.
interrupteur.addEventListener('click', () => {
  const actif = interrupteur.getAttribute('aria-checked') === 'true';
  interrupteur.setAttribute('aria-checked', String(!actif));
  enregistrerPreference({ notificationsEmail: !actif });
});

Piloter le style depuis l'attribut plutôt que depuis une classe CSS : le visuel ne peut alors jamais diverger de ce qui est annoncé.

/* L'état visuel dérive de l'attribut : une seule source de vérité. */
[role="switch"][aria-checked="true"] .interrupteur__rail {
  background: #0056b3;
}

/* Le curseur SE DÉPLACE : la position porte l'état, pas seulement
   la couleur. C'est ce qui rend l'état perceptible par tous (critère 3.1). */
[role="switch"][aria-checked="true"] .interrupteur__curseur {
  transform: translateX(1.375rem);
}

[role="switch"]:focus-visible {
  outline: 2px solid #0056b3;
  outline-offset: 2px;
}

Dans un formulaire, préférer la variante champ natif : sur <input type="checkbox" role="switch">, le navigateur dérive aria-checked de la propriété checked, il n'y a donc aucun script d'état à écrire, et la valeur est soumise avec le formulaire.

<div class="ligne-interrupteur">
  <input type="checkbox" role="switch" id="notif" name="notif">
  <label for="notif">Notifications par e-mail</label>
</div>

Enfin, ne pas ajouter aria-pressed en plus de aria-checked : ce serait cumuler deux mécanismes d'état sur un même contrôle.

Défauts fréquents et impact utilisateur

  • <div> cliquable avec curseur glissant : non focusable, sans rôle ni état. Un utilisateur de lecteur d'écran ignore jusqu'à l'existence du contrôle ; un utilisateur clavier ne peut pas l'atteindre. C'est l'interrupteur le plus répandu sur le web.
  • role="switch" sans aria-checked : annoncé « interrupteur » sans état. Le rôle promet une information qui n'est pas là (critère 7.1).
  • aria-pressed ajouté en plus de aria-checked : l'état est annoncé deux fois (« interrupteur, activé, pressé ») et l'utilisateur ne sait plus quelle information croire.
  • Étiquette qui alterne entre « Activé » et « Désactivé » : le nom et l'état prétendent tous deux porter l'état, et leurs annonces se contredisent (« Désactivé, interrupteur, activé »). Nommer le réglage, laisser aria-checked porter l'état.
  • État exprimé uniquement par la couleur du rail : sans déplacement du curseur, l'état est imperceptible pour un utilisateur qui ne distingue pas les couleurs, et disparaît totalement en mode contraste élevé de Windows, où les couleurs de fond sont supprimées (critère 3.1).
<!-- À ne pas faire : le nom porte l'état. Annoncé « Désactivé,
     interrupteur, désactivé », puis « Activé, interrupteur, activé ». -->
<button type="button" role="switch" aria-checked="false">Désactivé</button>

<!-- À faire : le nom désigne le réglage, aria-checked porte l'état. -->
<button type="button" role="switch" aria-checked="false"
        aria-labelledby="notif-label"></button>

Ce que les outils automatiques ne détectent pas

Les outils automatiques, notre scanner compris, repèrent bien un role="switch" sans nom accessible ou une valeur aria-checked invalide. Quatre points restent hors de leur portée :

  • Un état porté par la couleur seule : vérifier qu'un interrupteur reste lisible une fois désaturé, ou en mode contraste élevé, demande un contrôle visuel manuel (critère 3.1).
  • Une étiquette qui change avec l'état, ou qui nomme l'état au lieu du réglage : le nom accessible existe et paraît valide à un outil.
  • Un <div> interrupteur sans rôle : sans sémantique déclarée, il n'y a rien à analyser, donc rien à signaler. Seule une restitution au lecteur d'écran révèle l'absence du contrôle.
  • La distinction d'usage interrupteur / case à cocher (effet immédiat ou valeur soumise), qui relève de l'analyse du parcours.

Vérifier ce composant

Protocole manuel rapide :

  1. Au clavier seul : atteindre l'interrupteur avec Tab, vérifier que Espace bascule l'état. L'indicateur de focus doit être visible dans les deux états.
  2. Au lecteur d'écran : l'annonce attendue est « nom du réglage, interrupteur, activé/désactivé ». Si l'état est entendu deux fois, il y a un aria-pressed en trop ou une étiquette qui change avec l'état.
  3. Le test de la couleur : capturer l'interrupteur dans ses deux états et désaturer les captures. Si activé et désactivé deviennent indistinguables, l'état repose sur la couleur seule. Vérifier aussi le rendu en mode contraste élevé de Windows.

Deux outils gratuits du site aident à ce contrôle : le Simulateur de Daltonisme montre comment les deux états sont perçus avec une déficience de la vision des couleurs, et le Testeur Focus Visible valide vos styles CSS :focus sur le contrôle.

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