Aller au contenu principal
Technique25 avril 202610 min

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.
ARIABoutonsJavaScriptaccessibilitéWCAG

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">.

CODE
<!-- 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.

CODE
<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" :

CODE
<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 :

CODE
<!-- 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 :

CODE
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 :

CODE
<div role="button"
     tabindex="0"
     aria-pressed="false">
  Mode sombre
</div>

Le JavaScript doit mettre à jour cet attribut :

CODE
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 :

CODE
<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) :

CODE
<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é :

CODE
[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

Gestion des états visuels

Un bouton doit avoir des indicateurs visuels clairs :

CODE
[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

CODE
<!-- 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

CODE
<!-- 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

CODE
<!-- 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

CODE
<!-- 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-pressed si bouton bascule
  • aria-expanded et aria-haspopup si menu
  • aria-disabled si 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.

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.