Composant · Formulaires et saisie

Curseur accessible (slider) : role slider, clavier et RGAA

Construire un curseur (slider) accessible : input type range, role slider, aria-valuenow, clavier complet et alternative au glissement selon le RGAA.

Un curseur (slider) sert à choisir une valeur dans un intervalle en déplaçant une poignée le long d'une piste : volume sonore, prix maximal, niveau d'intensité. Côté HTML, l'élément à préférer est <input type="range"> : il apporte gratuitement le clavier complet, l'annonce de la valeur par les lecteurs d'écran, une vraie valeur de formulaire et, point souvent décisif pour la conformité, le réglage par simple clic sur la piste.

Un curseur reconstruit en <div> avec role="slider" doit réimplémenter tout cela à la main. C'est possible, mais chaque oubli devient une non-conformité : cette fiche détaille ce que le RGAA attend dans les deux cas.

Quand ce motif est le mauvais choix

  • Pour saisir une valeur exacte (un montant précis, une année de naissance), un champ numérique est plus rapide et plus fiable qu'une poignée à positionner au pixel près. Voir la fiche champ incrémental (spinbutton).
  • Pour un petit nombre d'options nommées (Faible / Moyen / Fort), des boutons radio ou un <select> sont plus simples à comprendre et mieux restitués par les technologies d'assistance.
  • Pour afficher une mesure que l'utilisateur ne peut pas modifier (score, espace disque), le bon motif est la jauge (meter), jamais un curseur désactivé.

Critères RGAA applicables

Interaction clavier attendue

ToucheAction
Flèche droite / Flèche hautAugmente la valeur d'un pas
Flèche gauche / Flèche basDiminue la valeur d'un pas
Début (Home)Va au minimum
Fin (End)Va au maximum
Page haut / Page basAugmente / diminue d'un pas plus grand (recommandé)

Seule la poignée est focusable : jamais de tabindex sur la piste, qui créerait un second arrêt de tabulation inutile. Avec <input type="range">, le champ est la poignée.

Rôles et attributs ARIA

Sur un curseur personnalisé, la poignée porte role="slider" avec trois attributs obligatoires (aria-valuenow, aria-valuemin, aria-valuemax) et un nom accessible (aria-label ou aria-labelledby). aria-valuetext remplace le nombre annoncé quand il ne parle pas de lui-même (« 250 € », « Moyen ») et aria-orientation="vertical" déclare un curseur vertical.

Sur un <input type="range"> natif, le navigateur dérive tout de value, min et max : ne pas redéclarer aria-valuenow, ce serait créer une seconde source de vérité qui finira par se périmer.

<!-- Curseur natif : le clavier, l'annonce de la valeur et le clic
     sur la piste sont fournis par le navigateur. -->
<div>
  <label for="volume">Volume</label>
  <input type="range" id="volume" name="volume"
         min="0" max="100" step="1" value="50">
  <!-- La valeur visible est un élément séparé : le champ ne peut pas
       être à la fois étiqueté « Volume » et porter sa valeur en texte. -->
  <output for="volume">50 %</output>
</div>

<!-- Échelle non numérique : aria-valuetext est annoncé À LA PLACE
     du nombre. « Moyen » plutôt que « 2 ». -->
<label for="epice">Niveau d'épice</label>
<input type="range" id="epice" min="1" max="4" step="1" value="2"
       aria-valuetext="Moyen">
<output for="epice">Moyen</output>

Si le curseur est vraiment reconstruit à la main, deux obligations concentrent l'essentiel du travail : le clavier complet et l'alternative au glissement.

// Test 13.10.2 : LA règle que les curseurs maison oublient.
// Un clic (ou une tape) sur un point de la piste doit régler la valeur,
// sans aucun glissement. Sans ce gestionnaire, la seule voie au pointeur
// est un geste de trajectoire : non conforme.
piste.addEventListener('pointerdown', (event) => {
  const rect = piste.getBoundingClientRect();
  reglerValeur(MIN + ((event.clientX - rect.left) / rect.width) * (MAX - MIN));
  poignee.focus();
});

// Critère 13.11 : valider au relâcher, jamais pendant le glissement,
// pour que l'utilisateur puisse abandonner son geste en cours.

Pour un curseur vertical, utiliser la propriété CSS writing-mode: vertical-rl plutôt qu'une rotation transform: rotate() : une rotation conserve la sémantique clavier horizontale (Flèche droite ferait monter la poignée) et le lecteur d'écran continue d'annoncer un curseur horizontal.

Le curseur à deux poignées (multi-thumb)

Une fourchette de prix (minimum et maximum) se règle avec un curseur à deux poignées sur la même piste. Il n'existe pas d'élément natif multi-poignées : la construction la plus accessible superpose deux <input type="range">, qui conservent le clavier natif et deux vraies valeurs de formulaire.

Trois exigences s'ajoutent à celles du curseur simple :

  • Deux étiquettes distinctes (critère 11.1) : « Prix » deux fois ne dit pas quelle extrémité on tient. Utiliser « Prix minimum » et « Prix maximum ».
  • Des bornes annoncées qui se contraignent mutuellement (critère 7.1) : le maximum annoncé de la poignée basse est la valeur courante de la poignée haute, et réciproquement, mis à jour à chaque mouvement. Des bornes figées mentent sur l'intervalle réellement atteignable.
  • Des champs numériques adjacents : c'est à la fois l'alternative en un point unique exigée par le test 13.10.2 et la meilleure expérience pour saisir des bornes exactes.
<fieldset>
  <legend>Fourchette de prix</legend>
  <!-- Chaque poignée a son propre nom accessible. -->
  <label for="prix-min">Prix minimum</label>
  <input type="range" id="prix-min" min="0" max="1000" step="10"
         value="200" aria-valuetext="200 €">
  <label for="prix-max">Prix maximum</label>
  <input type="range" id="prix-max" min="0" max="1000" step="10"
         value="800" aria-valuetext="800 €">
  <output>200 € à 800 €</output>
</fieldset>

Quand les poignées se rejoignent, bloquer (la poignée basse ne dépasse pas la haute) est plus simple à comprendre et à restituer qu'un échange silencieux des deux poignées, qui laisse le focus sur un élément dont le nom vient de changer.

Défauts fréquents et impact utilisateur

  • Curseur sans étiquette : le lecteur d'écran annonce « curseur, 50 ». 50 de quoi ? Impossible de savoir ce que l'on règle (critère 11.1).
  • Curseur utilisable uniquement au glissement : sans clic direct sur la piste ni champ numérique de repli, le seul chemin au pointeur est un geste de trajectoire, inaccessible à une personne ayant des tremblements ou utilisant un contacteur (test 13.10.2). C'est le défaut le plus répandu des curseurs maison.
  • role="slider" sans aria-valuenow : le rôle promet une valeur et il n'y en a pas ; le composant est annoncé vide (critère 7.1).
  • La valeur affichée utilisée comme étiquette : le nom accessible devient « 50 » et change pendant le glissement. Le nom d'un composant doit être stable, la valeur est une donnée séparée.
  • Clavier limité aux flèches gauche et droite : Début, Fin et Page haut / Page bas sont attendus (et gratuits avec l'élément natif) ; sans eux, atteindre une borne exige des dizaines d'appuis.
  • Nombre annoncé sans signification : « curseur, 2 » pour un niveau d'épice ne dit rien ; aria-valuetext="Moyen" porte le sens.

Ce que les outils automatiques ne détectent pas

  • L'absence d'alternative en un point unique au glissement (test 13.10.2) : seul un test manuel au pointeur, sans glisser, permet de vérifier que la valeur reste réglable.
  • Les touches manquantes (Début, Fin, Page haut / Page bas) sur un curseur personnalisé : cela se vérifie au clavier, touche par touche.
  • Un curseur vertical construit par rotation CSS, dont le clavier contredit le visuel.
  • Le moment du déclenchement, à l'appui ou au relâcher (critère 13.11) : un comportement dynamique hors de portée d'une analyse statique.

Les outils automatiques, dont notre scanner, repèrent en revanche un curseur sans étiquette ou un role="slider" sans aria-valuenow : une base utile, qui ne remplace pas la passe manuelle ci-dessous.

Vérifier ce composant

Protocole manuel, dans l'ordre où les défauts sont les plus probables :

  1. Au pointeur, sans glisser : cliquer ou taper sur un point de la piste. La valeur doit changer. Si seul le glissement fonctionne, le test 13.10.2 échoue.
  2. Au clavier seul : Tab jusqu'à la poignée (une seule tabulation pour tout le composant), puis flèches, Début, Fin, Page haut / Page bas. Chaque touche doit agir et la prise de focus rester visible.
  3. Au lecteur d'écran : attendre « étiquette, curseur, valeur ». Un nombre là où un mot est attendu (« 2 » pour un niveau d'épice) appelle aria-valuetext.
  4. Sur un curseur double : vérifier que chaque poignée annonce un nom distinct et des bornes qui suivent l'autre poignée.

Deux outils gratuits du site aident sur ce composant : le Testeur Focus Visible pour contrôler la prise de focus de la poignée (critère 10.7), et le Simulateur Handicap Moteur pour expérimenter ce qu'un glissement de poignée exige avec un pointeur altéré, et comprendre pourquoi l'alternative en un point unique n'est pas une option.

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