Boutons ARIA personnalisés : Guide technique accessibilité
En Bref : L'essentiel à retenir
- Privilégiez toujours élément button natif. N'utilisez role="button" que si un élément non-bouton doit se comporter comme tel.
- Un div avec role="button" doit être focusable (tabindex="0"), réagir à Entrée ET Espace, et avoir un nom accessible.
- Les boutons bascule nécessitent aria-pressed, les boutons menu aria-haspopup et aria-expanded pour communiquer leur état.
- Les lecteurs d'écran n'annoncent que le rôle, pas le comportement. Implémenter toute l'interactivité en JavaScript.
Les boutons sont l'élément interactif le plus courant du web. Pourtant, de nombreux développeurs créent des "boutons" avec des div ou des span qui ne sont pas accessibles. Quand l'élément natif <button> n'est pas utilisable, ARIA permet de recréer l'accessibilité, mais cela demande un travail rigoureux.
La première règle d'ARIA
Avant tout, rappelons la première règle d'ARIA : si vous pouvez utiliser un élément HTML natif avec la sémantique et le comportement souhaités, faites-le. Un <button> natif est toujours préférable à un <div role="button">.
<!-- préféré : élément natif -->
<button type="button">Sauvegarder</button>
<!-- À éviter si possible -->
<div role="button">Sauvegarder</div>
L'élément natif offre gratuitement : focus clavier, activation avec Entrée et Espace, annonce correcte aux lecteurs d'écran.
[!NOTE] Utilisez
role="button"uniquement quand des contraintes techniques ou de design empêchent l'utilisation d'un button natif.
Quand role="button" est nécessaire
Certaines situations légitimes nécessitent un bouton personnalisé :
- Un lien qui doit visuellement ressembler à un bouton mais déclencher une action
- Un élément interactif dans un composant tiers non modifiable
- Des contraintes CSS spécifiques incompatibles avec button
- Migration progressive d'un code legacy
Les exigences d'un bouton ARIA conforme
1. Sémantique avec role="button"
L'attribut role="button" indique aux technologies d'assistance que l'élément se comporte comme un bouton.
<div role="button">Mon bouton</div>
Mais ce n'est que le début. Le rôle seul ne fournit aucune fonctionnalité.
2. Focusabilité avec tabindex
Un bouton doit être atteignable au clavier. Ajoutez tabindex="0" :
<div role="button" tabindex="0">Mon bouton</div>
Cela permet à l'élément de recevoir le focus via Tab et d'être inclus dans l'ordre de tabulation naturel.
3. Nom accessible
Tout bouton doit avoir un nom accessible. Plusieurs méthodes :
<!-- Contenu texte (préféré) -->
<div role="button" tabindex="0">Sauvegarder</div>
<!-- aria-label pour boutons icône -->
<div role="button" tabindex="0" aria-label="Fermer">
<svg><!-- icône X --></svg>
</div>
<!-- aria-labelledby pour référence externe -->
<div role="button" tabindex="0" aria-labelledby="btn-label">
<span id="btn-label">Supprimer l'élément</span>
</div>
4. Gestion du clavier
C'est l'étape cruciale souvent négligée. Un bouton natif réagit à Entrée ET Espace. Vous devez reproduire ce comportement :
const button = document.querySelector('[role="button"]');
button.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); // Empêche le scroll avec Espace
button.click(); // Déclenche l'action
}
});
button.addEventListener('click', () => {
// Action du bouton
console.log('Bouton activé');
});
[!TIP] Testez toujours avec Tab + Entrée ET Tab + Espace. Un oubli fréquent est de ne gérer que Entrée.
Boutons bascule (toggle)
Les boutons à deux états (actif/inactif) nécessitent aria-pressed :
<div role="button"
tabindex="0"
aria-pressed="false">
Mode sombre
</div>
Le JavaScript doit mettre à jour cet attribut :
toggleButton.addEventListener('click', () => {
const isPressed = toggleButton.getAttribute('aria-pressed') === 'true';
toggleButton.setAttribute('aria-pressed', !isPressed);
// Appliquer l'action de bascule
});
Les valeurs possibles :
aria-pressed="false": non presséaria-pressed="true": presséaria-pressed="mixed": état mixte (rare)
Boutons menu
Un bouton qui ouvre un menu déroulant utilise aria-haspopup et aria-expanded :
<div role="button"
tabindex="0"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="mon-menu">
Options
</div>
<ul id="mon-menu" role="menu" hidden>
<li role="menuitem">Option 1</li>
<li role="menuitem">Option 2</li>
</ul>
Mettez à jour aria-expanded à l'ouverture/fermeture du menu.
Boutons désactivés
Pour un bouton désactivé, utilisez aria-disabled plutôt que l'attribut HTML disabled (qui ne fonctionne pas sur les div) :
<div role="button"
tabindex="-1"
aria-disabled="true">
Action indisponible
</div>
Notez le tabindex="-1" qui retire l'élément de la tabulation, et le style CSS associé :
[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
Gestion des états visuels
Un bouton doit avoir des indicateurs visuels clairs :
[role="button"] {
/* Style de base */
padding: 0.5rem 1rem;
border: 2px solid currentColor;
border-radius: 4px;
cursor: pointer;
}
[role="button"]:hover {
background-color: #f0f0f0;
}
[role="button"]:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
[role="button"]:active {
transform: translateY(1px);
}
[role="button"][aria-pressed="true"] {
background-color: #0066cc;
color: white;
}
[!NOTE] L'indicateur de focus doit avoir un contraste suffisant (critère RGAA 10.7). Utilisez notre calculateur de contraste pour valider.
Erreurs fréquentes à éviter
1. Oublier le clavier
<!-- MAUVAIS : pas de gestion clavier -->
<div role="button" tabindex="0" onclick="doSomething()">
Cliquez ici
</div>
Le onclick seul ne gère pas Entrée/Espace.
2. Utiliser href="#" au lieu de button
<!-- MAUVAIS : sémantique de lien, pas de bouton -->
<a href="#" onclick="doAction()">Supprimer</a>
<!-- BON : si vraiment nécessaire -->
<a href="#" role="button" onclick="doAction()"
onkeydown="if(event.key===' '){event.preventDefault();doAction()}">
Supprimer
</a>
Mieux : Utilisez simplement <button>.
3. role="button" sur un lien de navigation
<!-- MAUVAIS : c'est un lien, pas un bouton -->
<a href="/page" role="button">Voir la page</a>
Un élément qui navigue vers une URL est un lien, pas un bouton.
4. Nom accessible manquant
<!-- MAUVAIS : pas de nom -->
<div role="button" tabindex="0">
<svg><!-- icône sans description --></svg>
</div>
Checklist complète
Avant de considérer votre bouton ARIA comme accessible :
-
role="button"présent -
tabindex="0"pour le focus - Nom accessible (texte, aria-label, aria-labelledby)
- Gestion de la touche Entrée
- Gestion de la touche Espace (avec preventDefault)
- Indicateur de focus visible
-
aria-pressedsi bouton bascule -
aria-expandedetaria-haspopupsi menu -
aria-disabledsi désactivé - Contrastes suffisants (texte et focus)
Conclusion
Créer un bouton ARIA accessible demande plus d'effort qu'un simple <button>. Si vous avez le choix, préférez toujours l'élément natif. Mais quand role="button" est nécessaire, suivez rigoureusement toutes les exigences pour garantir une expérience équivalente.
Testez vos composants avec notre audit RGAA automatisé et utilisez notre générateur de patterns ARIA pour créer rapidement des composants accessibles conformes.
Guides RGAA associés
Pour aller plus loin sur les sujets abordés dans cet article, consultez nos fiches techniques :
Chaque champ de formulaire doit avoir une étiquette (label) qui lui est liée explicitement.
Chaque image porteuse d'information doit avoir une alternative textuelle pertinente via l'attribut alt. Les images décoratives doivent avoir un attribut alt vide.
L'intitulé de chaque lien doit être explicite et permettre de comprendre la destination ou la fonction du lien, même hors contexte.
Articles similaires
Votre site est-il conforme ?
Ne prenez pas de risques avec l'accessibilité. Lancez un audit complet de votre site en quelques minutes et obtenez un rapport détaillé des corrections à apporter.