Champ incrémental (spinbutton) : critères RGAA et ARIA
Champ incrémental accessible : input type number pour les quantités, inputmode numeric pour les chaînes de chiffres, role spinbutton et critères RGAA.
Un champ incrémental (spinbutton) restreint sa valeur à une plage de valeurs discrètes : un champ de saisie que l'on augmente et diminue aux flèches, souvent flanqué de boutons + et −. L'élément natif <input type="number"> fournit les flèches, l'annonce de la valeur et la valeur de formulaire. Mais il n'est le bon choix que pour de véritables quantités, et c'est la première question à trancher.
Quand ce motif est le mauvais choix
type="number" convient aux quantités sur lesquelles on ferait un calcul : quantité de panier, âge, prix, note. Il est incorrect pour les chaînes de chiffres, qui sont des identifiants et non des nombres :
| Donnée | Élément adapté |
|---|---|
| Quantité, âge, prix, note | <input type="number"> |
| Numéro de téléphone | <input type="tel"> (« +33 » n'est pas un nombre) |
| Carte bancaire, code postal, PIN, code à usage unique, IBAN, référence | <input type="text" inputmode="numeric"> |
Avec type="number" sur une chaîne de chiffres, les zéros de tête sont perdus (« 007 » devient « 7 »), la molette de la souris modifie silencieusement la valeur quand le champ a le focus, et certains navigateurs rejettent des caractères sans aucun retour. inputmode="numeric" affiche le clavier numérique sur mobile, ce que l'on cherche en réalité, sans aucun de ces défauts.
Pour choisir une valeur dans une plage de façon plus visuelle et continue, le motif curseur (slider) est parfois plus adapté que le champ incrémental.
Critères RGAA applicables
- Critère 11.1 : étiquette de champ. Le champ incrémental est un champ de formulaire et doit avoir une étiquette associée (
<label for>). « Spin button, 1 » sans étiquette ne dit pas de quoi il s'agit. - Critère 7.1 : compatibilité avec les technologies d'assistance. La valeur courante doit être exposée. Sur une version personnalisée
role="spinbutton",aria-valuenowest obligatoire, avecaria-valuemin/aria-valuemaxpour les bornes. - Critère 7.3 : contrôle par le clavier et tout dispositif de pointage. Les flèches haut/bas doivent modifier la valeur ; les boutons + et − doivent être atteignables au clavier ou explicitement exclus de l'ordre de tabulation au profit du champ.
- Critère 11.10 : contrôle de saisie pertinent. Les bornes doivent être indiquées visiblement avant validation, et une valeur hors bornes doit produire un message d'erreur identifiant le champ (ou
aria-invalid="true"). Réécrire silencieusement 999 en 10 ne constitue pas un contrôle de saisie pertinent. - Critère 10.7 : prise de focus visible. L'indicateur de focus du champ doit rester visible.
- Critère 11.13 : finalité de champ. Pour les chaînes de chiffres qui concernent l'utilisateur (code postal, téléphone, carte), un attribut
autocompletepertinent (postal-code,tel,cc-number) doit faciliter le remplissage automatique.
Interaction clavier attendue
| Touche | Action |
|---|---|
| Flèche haut | Augmente la valeur d'un pas |
| Flèche bas | Diminue la valeur d'un pas |
| Origine (Home) | Place la valeur au minimum, si des bornes existent |
| Fin (End) | Place la valeur au maximum, si des bornes existent |
| Page précédente / Page suivante | Optionnel : incrémente ou décrémente d'un pas plus grand |
La saisie directe au clavier doit rester possible : taper « 7 » est plus rapide que six appuis sur le bouton +.
Rôles et attributs ARIA
Avec <input type="number">, aucun rôle ARIA n'est nécessaire. Les boutons + et − personnalisés doivent soit porter un nom accessible (« Augmenter la quantité »), soit être retirés de la restitution (aria-hidden="true" et tabindex="-1"), puisqu'ils dupliquent les flèches déjà disponibles sur le champ. L'annonce d'un ajustement de valeur passe par une zone role="status".
<div class="quantite">
<label for="qte">Quantité</label>
<!-- tabindex="-1" et aria-hidden : ces boutons sont une commodité
pour le pointeur ; le champ gère déjà le clavier. -->
<button type="button" tabindex="-1" aria-hidden="true">−</button>
<input type="number" id="qte" name="qte"
min="1" max="10" step="1" value="1"
inputmode="numeric" aria-describedby="qte-aide">
<button type="button" tabindex="-1" aria-hidden="true">+</button>
<!-- Les bornes sont annoncées AVANT la validation (critère 11.10). -->
<p id="qte-aide">Maximum 10 par commande.</p>
<!-- Toujours présente dans le DOM, vide au repos : c'est ici que
l'ajustement d'une valeur hors bornes est annoncé. -->
<p role="status" class="sr-only"></p>
</div>
const champ = document.getElementById('qte');
const statut = document.querySelector('[role="status"]');
const MIN = 1, MAX = 10;
// Le correctif que tout le monde oublie : sans lui, faire défiler la
// page avec le champ focalisé change la quantité en silence.
champ.addEventListener('wheel', (evenement) => {
if (document.activeElement === champ) evenement.preventDefault();
}, { passive: false });
function bornerEtAnnoncer() {
const saisie = Number(champ.value);
if (Number.isNaN(saisie) || champ.value === '') return;
const bornee = Math.min(MAX, Math.max(MIN, saisie));
if (bornee === saisie) return;
champ.value = String(bornee);
// Ne jamais réécrire la saisie en silence (critère 11.10).
statut.textContent = `Quantité ramenée à ${bornee}. Maximum ${MAX} par commande.`;
}
// Borner à la perte de focus, jamais à chaque frappe : sinon taper
// « 10 » devient impossible, « 1 » étant corrigé avant le « 0 ».
champ.addEventListener('change', bornerEtAnnoncer);
Pour une chaîne de chiffres, qui n'est pas un champ incrémental, la bonne construction est la suivante :
<!-- Pas type="number" : un code postal n'est pas une quantité.
inputmode donne le clavier numérique sur mobile, autocomplete
sert la finalité du champ (critère 11.13). -->
<label for="code-postal">Code postal</label>
<input type="text" id="code-postal" name="code-postal"
inputmode="numeric" pattern="[0-9]{5}"
autocomplete="postal-code" aria-describedby="cp-aide">
<p id="cp-aide">5 chiffres, par exemple 75011.</p>
En version ARIA (si le natif est réellement impossible) : role="spinbutton", aria-valuenow obligatoire, aria-valuemin/aria-valuemax, un nom accessible, et toute la gestion clavier du tableau ci-dessus à réimplémenter.
Défauts fréquents et impact utilisateur
- Molette non neutralisée sur
type="number": le champ a le focus, l'utilisateur fait défiler la page, et la quantité change silencieusement. C'est un bug d'intégrité des données, pas un détail : la commande part avec la mauvaise quantité. type="number"pour une carte bancaire ou un code postal : zéros de tête perdus, caractères rejetés sans retour, flèches d'incrément dénuées de sens sur un identifiant.- Écrêtage silencieux : l'utilisateur tape 999, le champ se réécrit à 10 sans explication. Un utilisateur de lecteur d'écran n'apprend jamais que sa saisie a changé (critère 11.10).
- Boutons + et − sans nom, exposés aux technologies d'assistance : annoncés « moins, bouton » / « plus, bouton », ils encombrent la tabulation en dupliquant les flèches du champ.
- Champ en lecture seule pilotable uniquement aux boutons + et − : la saisie directe disparaît, le chemin le plus rapide vers une valeur est retiré sans bénéfice.
- Écrêtage à chaque frappe (dans
onChangeouoninput) : taper « 10 » devient impossible car « 1 » est corrigé avant la saisie du « 0 ». Écrêter à la perte de focus.
Ce que les outils automatiques ne détectent pas
Soyons précis sur le partage : l'absence d'étiquette ou un role="spinbutton" sans aria-valuenow sont bien détectés par les outils automatiques, notre scanner compris. Leur échappent en revanche :
- Le mauvais choix d'élément :
type="number"sur un numéro de carte est du HTML parfaitement valide. Seule l'analyse de la nature de la donnée le révèle. - Le bug de la molette et l'écrêtage silencieux d'une valeur hors bornes : deux comportements dynamiques qui exigent un test manuel (critère 11.10 pour le second).
- La pertinence de l'aide à la saisie : bornes annoncées avant validation, message d'ajustement réellement restitué par la zone de statut.
Vérifier ce composant
Protocole manuel rapide :
- Le test de la molette, en premier : focaliser le champ, puis faire défiler la page à la souris. Si la valeur change, le bug de corruption silencieuse est présent.
- Le test de l'élément : la donnée est-elle une quantité ? Taper « 007 » : si le champ affiche « 7 », c'est un
type="number"posé sur une chaîne de chiffres. - Le test des bornes : taper 999 avec un maximum de 10. Quelque chose doit signaler l'ajustement, visuellement et via la zone de statut (critère 11.10).
- Au clavier seul : les flèches haut/bas modifient la valeur, la saisie d'un nombre à deux chiffres fonctionne, et Tab ne s'arrête pas sur les boutons + et −.
Deux outils gratuits du site aident à ce contrôle : l'outil Patterns ARIA fournit des régions live (role="status") prêtes à coller pour l'annonce d'ajustement, et le Simulateur Lecteur d'Écran permet de vérifier le nom, le rôle et la valeur réellement exposés par le champ.
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