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
- Critère 3.3 : contraste des composants d'interface. La poignée et la piste stylées par l'auteur doivent présenter un rapport de contraste d'au moins 3:1 avec les couleurs contiguës.
- Critère 7.1 : scripts compatibles avec les technologies d'assistance. Le curseur doit exposer son nom, son rôle et sa valeur. Sur un curseur personnalisé,
aria-valuenow(etaria-valuetextquand le nombre brut n'est pas parlant) doit suivre chaque changement. - Critère 7.3 : scripts contrôlables au clavier et au pointeur. Les flèches, Début et Fin, et idéalement Page haut / Page bas, doivent ajuster la valeur sans souris.
- Critère 10.7 : prise de focus visible. La poignée doit montrer une prise de focus visible, qu'elle vienne du style natif conservé ou d'un style auteur suffisamment contrasté.
- Critère 11.1 : étiquette des champs de formulaire. Un
<input type="range">est un champ de formulaire : il lui faut une étiquette associée (<label for>,aria-labelouaria-labelledby). La valeur affichée n'est pas une étiquette. - Critère 13.10 : gestes complexes. Le test 13.10.2 vise les gestes basés sur le suivi d'une trajectoire, et faire glisser une poignée en est un. La même valeur doit être atteignable par un contact en un point unique : clic ou tape sur la piste, ou champ numérique adjacent.
- Critère 13.11 : annulation des actions au pointeur. L'action doit être validée au relâcher du pointeur, pour qu'un glissement en cours puisse être abandonné.
Interaction clavier attendue
| Touche | Action |
|---|---|
| Flèche droite / Flèche haut | Augmente la valeur d'un pas |
| Flèche gauche / Flèche bas | Diminue la valeur d'un pas |
| Début (Home) | Va au minimum |
| Fin (End) | Va au maximum |
| Page haut / Page bas | Augmente / 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"sansaria-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 :
- 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.
- 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.
- 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. - 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