Créer des boutons accessibles et dignes de ce nom en HTML

Sur le web, les boutons sont partout. Il est très rare de voir un site web sans bouton. En effet, les boutons servent à soumettre des formulaires ou à actionner des interactions gérées via JavaScript. En revanche, les boutons ne servent pas à aller sur une autre page web (ou à une zone de la page web en cours de consultation) ; ça, ce sont les liens qui le font.

Parfois, les liens ont des têtes de boutons ; on les appelle même les call to action (CTA) dans le jargon du marketing.

Parfois, et c’est pire que tout, des éléments ont des têtes de boutons mais, dans le code, ils ne sont ni des liens, ni des boutons. Ils sont une image, une div, un span, un élément a sans attribut href ou je ne sais quoi encore. Ils sont inutilisables au clavier ou avec une technologie d’assistance comme un lecteur d’écran et sont donc inaccessibles pour les personnes handicapées. Nous sommes là face à un problème d’accessibilité web critique que nous devons résoudre.

Je vais essayer d’expliquer au mieux ce qu’il faut faire pour avoir un code HTML sémantique, utilisable, accessible en ce qui concerne les boutons.

Des boutons à l’intitulé pertinent

Tout d’abord, peu importe la façon dont vous codez votre bouton à ce stade, votre bouton doit avoir un intitulé pertinent. Oubliez les « cliquez ici », « ici », « là », « cliquez-moi », « OK », etc.

Un intitulé pertinent, c’est un intitulé qui informe correctement et explicitement sur l’action que produit le bouton.

Par exemple, pour un formulaire de recherche, ce sera « Rechercher ». S’il y a plusieurs formulaires de recherche dans une même page, on précisera ce que chacun permet de rechercher : « Rechercher dans le site », « Rechercher un évènement », etc. afin que les personnes utilisant des technologies d’assistance puissent s’y retrouver quand elles listent les boutons de la page. Pour une FAQ avec des accordéons à plier et déplier, le bouton sera sur la question qui en fera un très bon intitulé.

L’idéal est que ces intitulés soient visibles pour tout le monde. En effet, on voit beaucoup de boutons-icônes dont on ne sait pas à quoi correspond l’icône. Même si ces boutons ont un intitulé conforme aux règles d’accessibilité via un texte masqué en CSS, il pourrait être plus difficile pour les personnes qui pilotent leur ordinateur à la voix, par exemple, d’activer ce bouton sans savoir comment le nommer. De plus, si l’icône n’est pas claire, les personnes vont devoir cliquer pour savoir ce qui se cache derrière, ce qui peut être angoissant quand on ne maîtrise pas les interfaces (la peur de faire une bêtise). Parfois, on pourra ne voir qu’une partie de l’intitulé et en masquer une partie de façon accessible ; c’est possible avec le texte masqué en CSS déjà cité ou encore en utilisant certains attributs ARIA (ce que je vous déconseille vivement car on peut faire pire que mieux en utilisant mal ARIA).

Quoiqu’il en soit, un bouton ne doit jamais être vide, il doit avoir un texte à l’intérieur même si on ne le voit pas (masquez-le accessiblement) !

Des boutons sémantiques et utilisables au clavier

Il y a plusieurs sortes de boutons et plusieurs façons de les coder. Essayons de passer ça en revue.

Les boutons de soumission de formulaire, des boutons type="submit" dans des <form>

On peut se dire qu’il est facile de créer un bouton de formulaire. Pourtant, il y a des règles à respecter et elles sont loin d’être connues par toutes les personnes qui codent des sites web, malheureusement.

Voici les règles pour créer un bouton de soumission de formulaire correctement fonctionnel et accessible :

  • Il devrait être dans une balise <form> afin qu’il soit possible de soumettre le formulaire en appuyant sur la touche Entrée lorsqu’on se trouve dans un des champs du formulaire (hors champs multiligne comme textarea) ;
  • Il ne devrait pas être :
    • un élément a (que celui-ci ait un attribut href ou pas) (bien sûr que j’ai déjà vu ça…) ;
    • un élément button ou input avec un type button.
  • Il devrait être un élément button ou input avec un type submit afin qu’il soit possible de soumettre le formulaire avec la touche Entrée. Exemple : <button type="submit">Rechercher</button> ou <input type="submit" value="Rechercher" />

Par défaut, un élément button est de type submit donc il n’est pas nécessaire de préciser son type. Cependant, cela peut permettre de clarifier le code.

Il y a également quelques règles de navigation clavier à prendre en compte qui sont totalement natives quand on utilise les bons éléments HTML :

  • Lorsqu’on est dans un champ, la touche Entrée permet de soumettre le formulaire ;
  • Lorsqu’on a le focus sur le bouton de soumission du formulaire, les touches Entrée et Espace permettent d’activer le bouton et de soumettre le formulaire.

Petit aparté : dans les formulaires, on voit parfois des boutons de réinitialisation. Ce sont des boutons de type reset. Ils étaient très à la mode il y a longtemps et on les voit beaucoup moins désormais. Ces boutons permettent de remettre les valeurs par défaut des champs d’un formulaire. Ergonomiquement, ils posent un problème car si on clique dessus par mégarde, on perd tout ce qu’on a renseigné. Si on les utilise, il faudrait les coupler avec une demande de confirmation avant de réinitialiser le formulaire.

(Je vous passe le sujet des boutons de type image parce que c’est assez rare de les utiliser de nos jours.)

Les boutons pour les scripts, des boutons type="button"

Les boutons utilisés pour actionner des scripts sont très souvent codés avec les mauvais éléments HTML. Le résultat produit est que les personnes utilisant des technologies d’assistance de type lecteurs d’écran, outil de contrôle à la voix, clavier, etc., ne peuvent pas actionner ces boutons. On voit un peu de tout dans les codes source : des images, des éléments div ou span, des éléments personnalisés comme monSite-button, des éléments a  avec ou sans attribut href, etc. Je connais même un de ces fameux outils de surcouche d’accessibilité qui a tout codé en éléments personnalisés et dans lequel aucun bouton ne fonctionne au clavier (un comble quand on prétend que ces outils rendent les sites accessibles).

Voici les règles pour créer un bouton d’action correctement fonctionnel et accessible :

  • Il ne devrait pas être :
    • une <div> ou un <span> : éléments non sémantiques, non actionables par défaut ;
    • un élément a avec attribut href ou non ;
    • un élément personnalisé comme monSite-button ;
    • une image ;
    • etc.
  • Il devrait être un élément button ou input avec un type button. Exemple : <button type="button">Menu</button> ou <input type="button" value="Menu" />. Un élément button est de type submit si on ne précise pas son type. Par conséquent, ne pas le préciser pourrait poser un problème si ce bouton se trouvait dans un formulaire form puisqu’il pourrait soumettre le formulaire !

    Dans le cas d’un bouton d’action JavaScript dans un formulaire, il est possible d’utiliser un élément button ou input de type submit tout en précisant que ça ne valide pas le formulaire grâce à l’attribut HTML formnovalidate. On fait généralement cela dans un but bien précis : on veut s’assurer que l’entièreté du formulaire et les scripts qu’il contient pour le remplir fonctionnent sans JavaScript activé. Dans ce cas, si on désactive JavaScript, l’action du bouton enverra, par exemple, une valeur à un champ masqué (<input type="hidden" />). Ainsi, cela permettra, au rechargement de la page (car c’est ce que fait l’action submit par défaut, sans JS), que l’action du bouton soit exécutée. On peut voir ce cas, par exemple, sur cette page de formulaire du CNED (archivée) qui a un bouton « Écouter le code de vérification » en <input type="submit" formnovalidate />. Si on désactive JavaScript sur le site, alors, l’action du bouton va recharger la page et la version audio du CAPTCHA sera lue (attention, il faut que le navigateur autorise la lecture d’audio automatique pour que ça fonctionne). En revanche, si JavaScript est activé, le CAPTCHA est lu sans rechargement de page.

    Si on ne gère pas cette possibilité de JS désactivé, il faudra donc utiliser un élément input type="button" ou button type="button" pour les boutons actionnant des scripts.

Du point de vue de la navigation clavier, un bouton HTML s’active nativement aussi bien avec la touche Entrée qu’avec la touche Espace du clavier.

On peut aussi faire des boutons avec des div et ARIA, mais…

Des boutons ARIA, comment faire ?

Il arrive parfois qu’on n’ait pas d’autre choix que de faire un bouton via ARIA ou qu’on n’ait pas le choix que de le recommander parce que certains contextes projet nous empêche d’utiliser un bouton HTML ou parce que l’équipe de développement n’a pas trop envie de tout refaire son HTML…

Cette technique n’est à utiliser qu’en dernier recours, dans des cas bien spécifiques où l’utilisation d’un bouton ne serait vraiment vraiment pas possible (je n’ai même pas d’exemple sous la main). C’est le moment de vous rappeler la première règle d’ARIA : pas d’ARIA est mieux que du mauvais ARIA. Un élément HTML existe ? Utilisez-le !

Bon, admettons que vous n’ayez vraiment mais alors vraiment pas le choix, voici les règles à suivre :

  • Sur l’élément qui est un bouton d’action, ajouter un attribut role="button" pour lui donner une sémantique de bouton.
    Attention ! Le role="button" ne peut pas se mettre sur tous les éléments HTML. Par exemple, il n’est pas autorisé sur un titre car il casserait alors sa sémantique de titre. Cherchez votre élément HTML dans ce tableau de la spécification ARIA in HTML pour vérifier ce que vous avez le droit d’utiliser comme rôle dessus ;
  • Si cet élément n’est pas un élément a avec un attribut href="#", lui ajouter un attribut tabindex="0" afin qu’on puisse l’atteindre au clavier ;
  • Créer un petit script en JS pour faire en sorte que tous les éléments avec un role="button" soient activables via la touche Espace (ils le seront déjà avec la touche Entrée).
    Attention ! Combien de fois ai-je vu, lors des audits d’accessibilité, des boutons HTML button avec un role="button" dont l’activation avec la touche Espace faisait une double action (par exemple : ça ouvrait et fermait aussitôt un accordéon !). Ce comportement se produit car button a déjà un comportement natif via la touche Espace et le script lui force le même comportement ; cela fait donc doublon. Dans le JS, prévoyez bien que le script ajoutant ce comportement le fasse pour tous les éléments avec role="button" qui ne soient pas eux-mêmes des button ou input type="button" ou input type="submit". Mettre un role="button" sur un <button> n’a aucun intérêt, bien sûr ; c’est souvent lié au remplacement d’une div par le bon élément HTML mais sans vérifier les attributs présents…

Des boutons ARIA, je ne le recommande pas

Je ne recommande absolument pas d’utiliser les boutons ARIA pour plusieurs raisons.

  1. Il existe un élément HTML natif qui permet de faire ça. Pourquoi se compliquer la vie en surchargeant un élément non sémantique pour qu’il ait le même rôle et fonctionnement qu’un élément déjà existant et en réinventant la roue, finalement ?
  2. D’ailleurs, l’utilisation du mode de contraste élevé sous Windows stylera les boutons HTML comme demandé dans les préférences de l’ordinateur mais pas les boutons ARIA à moins d’utiliser la media query -ms-high-contrast pour forcer les styles du bouton ARIA. Vous pouvez jouer avec ce CodePen sous le navigateur Edge si vous êtes sous Windows.
    Comparaison du rendu par défaut d’un bouton HTML et d’un bouton ARIA en mode de contraste élevé sous Windows
    Comparaison du rendu par défaut d’un bouton HTML et d’un bouton ARIA en mode de contraste élevé sous Windows. On voit que j’ai choisi que le texte par défaut soit écrit en jaune et que le texte des boutons soit écrit en blanc. Sur la capture, le bouton HTML a bien un texte blanc et le bouton ARIA a un texte jaune.
  3. Il n’y a pas de rôle ARIA pour indiquer qu’un bouton est de type submit ni aucune fonctionnalité ARIA qui permettrait automatiquement de mimer son comportement. Par conséquent, si on veut le fonctionnement natif du formulaire qui s’envoie en faisant Entrée dans un champ de formulaire (autre qu’un champ multiligne), il faudra le recoder.
  4. ARIA n’est pas une spécification simple à appréhender et peu de monde la connaît vraiment bien. Par conséquent, il y a fort à parier que la maintenabilité de ce code soit mise en péril par l’intervention future d’une personne ne connaissant pas du tout ARIA et l’accessibilité et se dise mais qu’est-ce que c’est que ce truc ? Ça sert à rien ! et supprime une partie du code (HTML ou JS) qui permettait de mettre un pansement sur ce code non sémantique et non fonctionnel. Oui, ça se voit tous les jours ce genre de régression lors des audits d’accessibilité. Je rappelle donc à nouveau la première règle d’ARIA : pas d’ARIA est mieux que du mauvais ARIA.
  5. Utiliser un élément div au lieu de button laisse croire aux personnes non connaisseuses qu’il est possible de mettre n’importe quel élément HTML dedans. Cela multiplie les « chances » de faire des bêtises. Je vous laisse lire le chapitre suivant à ce sujet.
  6. Je vous ai parlé au chapitre précédent du problème de JavaScript où des éléments button avec un role="button" ont un comportement tout cassé lors de l’activation de la touche Espace. Réinventer la roue, ce n’est pas simple ! Les boutons ARIA sont trop souvent mal codés.
  7. Vous avez vu le comportement en mode de contraste élevé sous Windows ? Bien, alors, maintenant, sachez qu’il y a des personnes handicapées qui naviguent avec des feuilles de style personnalisées. Ces feuilles de style peuvent venir en complément de celles du site web en le surchargeant ou bien, elles peuvent venir en remplacement pour styler quelques éléments HTML de base et désactiver tout le reste. Utiliser les éléments HTML natifs est une façon beaucoup plus sûre pour que tout le monde puisse avoir accès à l’information et utiliser vos sites web correctement : un bouton HTML non stylé s’identifie clairement dans une page. Un bouton ARIA, quant à lui, n’est qu’un bout de texte comme un autre.

Boutons HTML ou ARIA, on ne met pas n’importe quoi dedans

Que peut-on mettre comme éléments enfants d’un bouton HTML ou ARIA ? Réponses en citation.

Contenu autorisé dans un bouton HTML :

Phrasing content, but there must be no interactive content descendant and no descendant with the tabindex attribute specified.

Spécification HTML Living Standard

Contenu autorisé dans un bouton ARIA :

Phrasing content, but there must be no interactive content descendant.

Spécification du W3C « ARIA in HTML »

Bref, bouton HTML ou bouton ARIA, même combat ! Un bouton ne doit pas contenir d’éléments HTML autres que des « contenus phrasés » non interactifs (oui oui, on a déjà vu des liens dans des boutons et inversement, même si ça paraît logique que ça n’a aucun sens de faire ça). Ce qui revient à dire qu’on peut, à priori, y mettre les éléments suivants : abbr, b, bdi, bdo, br, canvas, cite, code, data, datalist, del, dfn, em, i, img (sans attribut usemap), input type="hidden", ins, kbd, mark, meter, noscript, object (sans attribut usemap), output, picture, progress, q, ruby, s, samp, script, small, span, strong, sub, sup, svg, template, time, u, var, video (sans attribut controls donc autant dire qu’il ne faut pas l’utiliser dans un bouton car une vidéo doit pouvoir être contrôlée pour raison d’accessibilité), wbr.

Pourquoi je vous explique ça ?

Premièrement, parce que beaucoup trop de gens mettent des div dans leurs boutons alors que ce n’est pas autorisé. Cela peut causer des incompatibilités avec certaines technologies d’assistance car le code produit n’est pas robuste. Si vous aimez les div, apprenez à aimer les span qui, en tant qu’élément de type en ligne, sont autorisés.

Deuxièmement, parce que j’ai vu des projets où des boutons ARIA sous la forme <div role="button" tabindex="0"> étaient utilisés afin de pouvoir y mettre, entre autres, des titres (un h3, par exemple) parce qu’on peut mettre un h3 dans une div mais pas dans un button. Mais, à partir du moment où cette div (ou autre élément HTML) a un attribut role="button", alors elle se comporte comme un bouton HTML. Par conséquent, on ne peut pas et on ne doit pas mettre un titre dedans. Utiliser l’élément HTML natif aurait permis de repenser la conception de la fonctionnalité et d’éviter l’erreur. Résultat, il a fallu tout repenser une fois le code terminé.

Quelle incidence ? La sémantique du titre est écrasée et non restituée par les lecteurs d’écran (le titre est ainsi devenu un élément de présentation). Il existe des lecteurs d’écran qui ont décidé de patcher ça et restituent quand même la sémantique du titre. En effet, les gens font tellement n’importe quoi dans leur code qu’il a été décidé que faire ce patch était plus important que de respecter la spécification afin d’éviter d’avoir trop de pertes d’information pour les utilisateurs et utilisatrices. Par exemple, VoiceOver n’indique pas qu’il y a un titre de niveau 3 dans un bouton ARIA mais NVDA, oui. VoiceOver fonctionne conformément à la spécification et on n’a rien à lui reprocher.

Bref, vérifiez la validité de votre code HTML avec le « validateur W3C » comme on l’appelle, de son petit nom « Nu Html Checker ».

Styler l’élément button, ce n’est pas si compliqué

Une des raisons invoquées pour ne pas utiliser de boutons HTML mais des boutons ARIA à base de div, c’est la complexité à les styler en CSS. Les boutons ont en effet quelques particularités selon les navigateurs et peuvent donner du fil à retordre par défaut. Pourtant, il existe, depuis très longtemps, des solutions pour oublier ces complexités. Et, par ailleurs, vous avez vu ci-dessus que faire des boutons ARIA accessibles n’est pas si simple !

Et si on arrêtait de se faire une montagne du style des boutons ? Voyons comment c’est, en réalité, si simple à réaliser quand on a les bons outils et les bonnes techniques.

Et si cela vous semble encore trop complexe et que vous détestez CSS à ce point, laissez ce travail aux personnes dont c’est le métier, les intégratrices et intégrateurs web ;-)

Un style identique pour tous les navigateurs

On peut apprendre beaucoup en s’inspirant ou en utilisant les outils existants. Normalize.css, par exemple, donne la formule pour que tous les boutons soient uniformes sur tous les navigateurs. L’extrait ci-dessous permet d’harmoniser les différences pour les styles des boutons.

Je vous encourage à utiliser la feuille de style complète de Normalize.css dans vos projets et à ne pas la modifier ; ainsi, il n’y aura pas de risque de régression ou de non-maintenabilité.

/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css --- Extract */

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers.
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: inherit; /* 1 */
  font-size: 100%; /* 1 */
  line-height: 1.15; /* 1 */
  margin: 0; /* 2 */
}

/**
 * Show the overflow in IE.
 * 1. Show the overflow in Edge.
 */

button,
input { /* 1 */
  overflow: visible;
}

/**
 * Remove the inheritance of text transform in Edge, Firefox, and IE.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select { /* 1 */
  text-transform: none;
}

/**
 * Correct the inability to style clickable types in iOS and Safari.
 */

button,
[type="button"],
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

Après avoir intégré la feuille de style normalize.css à votre projet, vous n’avez plus qu’à styler vos boutons comme n’importe quel autre élément HTML : background-color, color, border, padding, etc.

En revanche, par dessus tout, on n’utilise pas width, height, max-width, max-height, overflow: hidden;. Pourquoi ? Parce que c’est la propriété CSS padding qui sert à mettre de l’espace à l’intérieur d’un élément, pas width ou height et que si on bloque les largeurs et hauteurs des éléments alors :

  • Si le texte est plus long que prévu (en version allemande, par exemple), ça ne rentrera pas ;
  • Si une personne choisit de zoomer le texte dans votre site ou dans son navigateur (en mettant plus que 16px comme taille de texte par défaut), une partie du texte du bouton sera masquée. Il sera illisible.

J’attire votre attention sur les deux dernières parties du code Remove the inner border and padding in Firefox et Restore the focus styles unset by the previous rule. Dans Firefox, par défaut, l’outline n’est pas géré de la même façon que dans les autres navigateurs sur les boutons au focus. Il s’agit d’une bordure interne en pointillés qui entoure le texte dans le bouton et non pas le bouton lui-même. Par défaut, il y a également une bordure bleue qui apparaît autour du bouton cette fois et encore une autre bordure grise autour.

Par conséquent, il arrive qu’en fonction des styles appliqués aux boutons, le focus devienne invisible alors même qu’on n’a jamais forcé l’outline à la valeur 0. L’ajout de ces deux dernières parties de code permettra alors de faire fonctionner l’outline de la même façon sur tous les navigateurs (bien que l’apparence diffère entre pointillés et trait plein).

L’outline est nécessaire

Quand on navigue dans une page web à la souris, on voit bien le pointeur qui nous indique où on se trouve.

Quand on navigue au clavier, on a aussi besoin de voir où on est dans la page web. Au clavier, ce qui indique où on se trouve dans la page quand on navigue de tabulation en tabulation, c’est l’outline. Cette espèce de bordure en pointillés sur Firefox et cette bordure noire aux coins arrondis (anciennement halo bleu) sur Chrome.

Si, dans la plupart des cas, il peut être admis d’utiliser un style de remplacement comme une bordure au lieu de l’outline de base, vous devrez toutefois veiller à ce que ce style de remplacement soit suffisamment visible (contraste, épaisseur…). Dans la future version des WCAG, 2.2, un nouveau critère permettant de bien définir ce que signifie un style de focus visible est en cours d’écriture.

Ce qui embête la plupart des gens, c’est que l’outline est également visible quand on clique à la souris sur un bouton. Ce comportement est tout à fait normal puisqu’en cliquant sur le bouton, on lui donne le focus. Et l’outline s’active au focus.

En attendant que la pseudo-classe CSS :focus-visible soit supportée par les navigateurs et intégrée dans la spécification CSS, on peut trouver ce polyfill « focus-visible » en JavaScript pour mettre en place ce comportement : rendre le focus visible (l’outline) uniquement en navigation clavier et non au clic souris.

Il n’y a plus aucune raison de désactiver complètement l’outline !

Flexbox sur les éléments button, ce n’est plus un problème

Il y avait, auparavant, un bug dans les navigateurs qui faisait que Flexbox n’était pas utilisable sur les éléments button ; ça ne fonctionnait tout simplement pas. Je me souviens avoir rencontré ce problème en 2018 sur un projet. On avait un bouton avec une icône et un texte qu’on devait aligner de façon centrée verticalement. On avait donc dû le faire à l’ancienne à base de display: inline-block; et vertical-align.

Aujourd’hui, ce bug semble bien être résolu si on en croit ce ticket Webkit et ce petit CodePen que j’ai fait rapidement pour tester (même dans Internet Explorer 11, ça marche !).


En conclusion, rendez le web meilleur en utilisant des boutons HTML natifs : utiliser une div n’est pas plus facile qu’utiliser un button directement !

Ressources complémentaires

Dans cet article, je me suis principalement concentrée sur la sémantique (donc, le rôle) des éléments et leur comportement natif. Je n’ai volontairement pas abordé les différents états et paramétrages que les boutons peuvent avoir, via ARIA (aria-expanded, aria-pressed, etc.), selon des contextes particuliers (accordéons, onglets, etc.) car il s’agit d’un sujet qui pourrait faire un autre (ou plusieurs) long article. D’autres articles ci-dessous abordent le sujet.

Un grand merci à Romain Gervois pour ses lumières sur l’attribut formnovalidate et sur ARIA, depuis déjà plus de 2 ans à travailler ensemble ! Cet article aurait été bien moins complet si je l’avais publié en 2018 quand j’ai commencé à l’écrire ;-)

2 commentaires sur « Créer des boutons accessibles et dignes de ce nom en HTML »

  1. Un grand merci à vous, Julie, pour cette parfaite mise au point ; Et je suis sincèrement ravie de découvrir votre blog que je vais suivre sans modération

Les commentaires sont fermés.