Bouton accessible : nom, états ARIA et critères RGAA
Le contrat du bouton accessible : élément button natif, nom accessible des boutons icône, états aria-pressed et aria-disabled, clavier Entrée et Espace.
Un bouton déclenche une action : soumettre un formulaire, ouvrir une boîte de dialogue, supprimer un élément. L'élément natif <button type="button"> est le seul point de départ raisonnable : il fournit le focus, Entrée, Espace, le rôle et le nom sans une ligne de script. Il n'existe pratiquement aucune raison valable de construire un bouton à partir d'un <div>.
Cette fiche est le contrat de référence du motif : nom accessible, états, clavier et critères RGAA. La difficulté réelle du bouton n'est presque jamais technique, elle est sémantique (est-ce vraiment un bouton ?) et nominale (que dit-il de lui-même ?).
Quand ce motif est le mauvais choix
La distinction bouton / lien structure tout le motif :
<button> | <a href> | |
|---|---|---|
| Vocation | fait quelque chose sur place | va quelque part |
| Entrée | active | active |
| Espace | active | ne fait rien (défilement de page) |
| Ouvrir dans un nouvel onglet | non | oui |
| Annoncé | « bouton » | « lien » |
| Exemples | Enregistrer, Supprimer, Ouvrir la modale | Accueil, Page suivante, Télécharger |
- Le contrôle navigue (change l'URL, ouvre une page) : c'est un lien
<a href>, même s'il est stylé comme un bouton. Seul un lien offre l'ouverture en nouvel onglet, le clic molette et l'aperçu de destination. Un bouton qui faitlocation.hrefest annoncé « bouton » alors qu'il va quelque part (critère 6.1). - Le contrôle agit : c'est un bouton. Un
<a href="#" onclick>est annoncé « lien » alors qu'il agit, et remonte en haut de page si le script échoue. - Le contrôle affiche ou masque un contenu : c'est un bouton de divulgation, avec
aria-expanded(motif disclosure, distinct). - Le contrôle maintient un réglage à effet immédiat : voir la fiche interrupteur (
role="switch").
Repère rapide : Entrée active les deux ; Espace n'active que les boutons ; « ouvrir dans un nouvel onglet » n'existe que sur les liens. Pour le pas-à-pas complet de cette décision, cas ambigus compris : Boutons vs liens : quand utiliser chaque élément HTML.
Critères RGAA applicables
- Critère 7.1 : scripts compatibles avec les technologies d'assistance. Un bouton généré ou contrôlé par script doit exposer son nom, son rôle et ses changements d'états (
aria-pressed,aria-expanded), et son nom accessible doit contenir au moins l'intitulé visible. - Critère 7.3 : scripts contrôlables au clavier et au pointeur. Le bouton doit s'activer avec Entrée et avec Espace, et fonctionner avec tout dispositif de pointage.
- Critère 11.9 : intitulé de bouton pertinent. Dans un formulaire, l'intitulé du bouton doit dire ce qu'il fait : « Créer mon compte » est pertinent là où « Valider » est faible.
- Critère 6.1 : lien explicite. Uniquement quand le contrôle est en réalité un lien : un contrôle qui navigue doit être un
<a href>dont l'intitulé permet de comprendre la destination. - Critère 10.7 : prise de focus visible. Ne jamais supprimer l'outline de focus sans le remplacer par un style visible.
Interaction clavier attendue
| Touche | Action |
|---|---|
| Entrée | Active le bouton. |
| Espace | Active le bouton. Si Espace fait défiler la page au lieu d'activer, le composant n'est pas un vrai bouton. |
C'est tout, et c'est le point fort du motif : aucun modèle clavier à apprendre, à condition d'utiliser l'élément natif.
Rôles et attributs ARIA
Sur un <button> natif, aucun role n'est nécessaire. Trois règles concentrent l'essentiel du contrat :
- Toujours écrire l'attribut
type. Dans un<form>, un<button>sanstypevauttype="submit": la source classique de soumissions accidentelles (le bouton « Afficher le mot de passe » qui envoie le formulaire). - Un bouton icône seule exige un nom accessible : du texte masqué visuellement (préférable, car la commande vocale « clique Supprimer » fonctionne et le texte survit à la traduction automatique) ou un
aria-label, avecaria-hidden="true" focusable="false"sur le SVG. - Un seul mécanisme d'état par bouton :
aria-pressed(bouton bascule) ouaria-expanded(divulgation), jamais les deux.
<!-- Bouton d'action simple : type="button" n'est pas optionnel. -->
<button type="button">Enregistrer le brouillon</button>
<!-- Soumission : dire ce que fait le bouton, pas « Valider » (critère 11.9). -->
<button type="submit">Créer mon compte</button>
<!-- Icône seule : le nom vit dans du texte masqué, l'icône est décorative. -->
<button type="button" class="bouton-icone">
<svg aria-hidden="true" focusable="false" width="16" height="16">
<use href="#icone-corbeille"/>
</svg>
<span class="sr-only">Supprimer le brouillon</span>
</button>
<!-- Bouton bascule : aria-pressed porte l'état, l'intitulé reste stable. -->
<button type="button" aria-pressed="false">
<svg aria-hidden="true" focusable="false"><use href="#icone-gras"/></svg>
<span class="sr-only">Mettre en gras</span>
</button>
<!-- Indisponible, mais l'utilisateur doit comprendre pourquoi :
aria-disabled laisse le bouton focusable et annoncé. -->
<button type="button" aria-disabled="true" aria-describedby="raison-publier">
Publier
</button>
<p id="raison-publier">Ajoutez un titre avant de publier.</p>
<!-- La navigation est un lien, même stylé en bouton. -->
<a href="/tarifs" class="bouton">Voir les tarifs</a>
aria-disabled est préférable à disabled quand l'utilisateur doit découvrir pourquoi le bouton est indisponible : disabled retire le bouton de l'ordre de tabulation (l'explication devient inatteignable), aria-disabled le laisse focusable et annoncé. En contrepartie, il n'empêche pas l'activation : c'est au script de bloquer l'action.
/* Critère 10.7 : ne jamais supprimer l'outline sans le remplacer. */
button:focus-visible,
.bouton:focus-visible {
outline: 2px solid #0056b3;
outline-offset: 2px;
}
/* aria-disabled n'est que sémantique : le style reste à votre charge. */
button[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
// aria-disabled ne bloque PAS l'activation : c'est le prix de rester
// focusable. Garder soi-même le gestionnaire.
boutonPublier.addEventListener('click', (event) => {
if (boutonPublier.getAttribute('aria-disabled') === 'true') {
event.preventDefault();
return;
}
publier();
});
Et les contre-exemples qui concentrent les non-conformités du motif :
<!-- À ne pas faire : un div en guise de bouton. Non focusable, sans rôle,
sans Entrée ni Espace : il faut ensuite tabindex, role ET un
gestionnaire clavier pour recréer <button> en moins bien. -->
<div class="bouton" onclick="enregistrer()">Enregistrer</div>
<!-- À ne pas faire : <button> sans type dans un formulaire.
Vaut type="submit" : chaque clic envoie le formulaire. -->
<form>
<button onclick="basculerMotDePasse()">Afficher le mot de passe</button>
</form>
<!-- À ne pas faire : un bouton qui navigue. Pas de nouvel onglet, pas de
clic molette, annoncé « bouton » alors qu'il va quelque part
(critère 6.1). -->
<button type="button" onclick="location.href='/tarifs'">Voir les tarifs</button>
<!-- À ne pas faire : deux mécanismes d'état cumulés.
« Gras, bouton bascule, activé, déplié » : lequel croire ? -->
<button type="button" aria-pressed="true" aria-expanded="true">Gras</button>
<!-- À ne pas faire : un intitulé qui change avec l'état.
« Réactiver le son, activé » : le nom et l'état se contredisent.
Nommer la FONCTION, laisser aria-pressed porter l'état. -->
<button type="button" aria-pressed="true">Réactiver le son</button>
Dernier recours : si un élément non natif doit absolument se comporter en bouton (contrainte de framework, DOM imposé), la reconstruction complète (rôle, tabindex, Entrée, Espace) est détaillée dans Boutons ARIA personnalisés : Guide technique accessibilité.
Défauts fréquents et impact utilisateur
<div onclick>en guise de bouton : non focusable, sans rôle, sans Entrée ni Espace. Un utilisateur clavier ne peut ni l'atteindre ni l'activer ; un lecteur d'écran n'annonce rien d'actionnable (critères 7.1 et 7.3).<button>sanstypedans un formulaire : chaque clic soumet le formulaire ; l'utilisateur perd sa saisie en voulant simplement afficher son mot de passe.- Bouton icône sans nom accessible : annoncé « bouton » tout court ; l'utilisateur de lecteur d'écran ignore ce que le bouton fait. C'est l'un des défauts les plus répandus du web.
aria-labelqui ne contient pas l'intitulé visible (« Envoyer le formulaire » sur un bouton affichant « Sauver ») : un utilisateur de commande vocale dit ce qu'il voit, et la commande échoue (critère 7.1, également critère 11.9 en formulaire).- Deux mécanismes d'état cumulés (
aria-pressedetaria-expanded) : deux états annoncés, aucun compréhensible. disabledsur un bouton dont une infobulle explique l'indisponibilité : le bouton n'est plus focusable, donc l'explication est inaccessible précisément quand elle est nécessaire.
Ce que les outils automatiques ne détectent pas
- Un bouton qui devrait être un lien (ou l'inverse) : la sémantique correcte dépend de ce que fait le contrôle, ce qui relève de l'analyse humaine.
- L'attribut
typemanquant dans un formulaire : le HTML est valide, seul le comportement de soumission le révèle. - Un intitulé qui contredit l'état (
aria-pressed="true"sur un bouton nommé « Activer le son ») ou un intitulé non pertinent au sens du critère 11.9 : la pertinence est un jugement.
Les boutons icône sans nom et les onclick posés sur des éléments non interactifs sont en revanche bien détectés par les outils automatiques, dont notre scanner : c'est l'un des motifs où l'automatisation rend le plus service, sans dispenser des vérifications sémantiques ci-dessus.
Vérifier ce composant
Protocole manuel rapide, dans cet ordre :
- Au clavier seul : Tab atteint le bouton, Entrée ET Espace l'activent, l'indicateur de focus est visible. Si Espace fait défiler la page, ce n'est pas un vrai bouton.
- Le test lien / bouton : l'activation change-t-elle l'URL ? Alors le contrôle doit être un
<a href>; un clic droit doit proposer « Ouvrir dans un nouvel onglet ». - Au lecteur d'écran, sur chaque bouton icône : attendre « Supprimer le brouillon, bouton », pas « bouton » tout court.
- À la commande vocale (ou mentalement) : prononcer l'intitulé visible doit suffire à viser le bouton ; si un
aria-labell'a remplacé par autre chose, la commande échoue.
Deux outils gratuits du site aident à ce contrôle : le quiz Bouton ou Lien ? entraîne précisément le réflexe sémantique du point 2, et le Testeur Focus Visible valide vos styles CSS :focus pour la navigation au clavier (critère 10.7).
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