Technique8 octobre 202613 min

Bouton radio HTML accessible : code, CSS et critères RGAA

En Bref : L'essentiel à retenir

  • Un groupe de boutons radio HTML se définit par un attribut name commun ; un seul bouton peut être coché, et si aucun ne l'est, le groupe est absent des données envoyées (MDN).
  • Le test 11.5.1 du RGAA 4.1.2 cite explicitement les input type="radio" : regroupés dans un <fieldset>, un role="group" ou un role="radiogroup", avec une légende (11.6) pertinente (11.7).
  • Pour personnaliser un bouton radio en CSS, masquez l'input par clip ou appearance: none, jamais par display: none, et redonnez un style :focus-visible : sinon le critère 10.7 tombe.
HTMLFormulairesRGAACSSComposantsAccessibilité

Le bouton radio est le composant de formulaire le plus simple à écrire en HTML, et l'un des plus souvent abîmés : masqué par display: none pour être restylé, privé de sa légende, coché par défaut sans raison, ou réécrit en <div> avec des attributs ARIA qu'il n'avait pas besoin de porter.

Ce guide reprend le composant de bout en bout : le balisage natif, le choix entre bouton radio, case à cocher et liste déroulante, ce que le navigateur fait déjà au clavier, la personnalisation CSS qui ne casse rien, puis les six critères du RGAA 4.1.2 (avril 2023) qu'un groupe de boutons radio engage, avec ce qu'un scan détecte et ce qui reste à vérifier à la main.

Commençons par le code qui marche.

Le bouton radio HTML natif : le balisage de référence

Un bouton radio est un <input type="radio">. Le groupe se définit par l'attribut name : tous les boutons qui partagent le même name forment un ensemble dont un seul membre peut être coché. MDN le résume ainsi : « cliquer sur une option désélectionne automatiquement l'option précédemment choisie dans ce groupe ».

Le balisage complet tient en quelques lignes :

CODE
<fieldset>
  <legend>Mode de livraison</legend>

  <input type="radio" id="livraison-standard" name="livraison" value="standard" required>
  <label for="livraison-standard">Standard (3 à 5 jours)</label>

  <input type="radio" id="livraison-express" name="livraison" value="express">
  <label for="livraison-express">Express (24 h)</label>

  <input type="radio" id="livraison-retrait" name="livraison" value="retrait">
  <label for="livraison-retrait">Retrait en boutique</label>
</fieldset>

Quatre éléments portent toute l'accessibilité :

  • <fieldset> et <legend> : la question (« Mode de livraison ») est la légende du groupe. Un lecteur d'écran l'annonce en entrant dans le groupe, avant la première option.
  • <label for> relié à l'id de chaque bouton : l'étiquette devient cliquable, ce qui agrandit la zone de sélection, et le lecteur d'écran lit « Standard (3 à 5 jours), bouton radio ».
  • name identique sur les trois boutons : sans lui, vous avez trois cases indépendantes qui ressemblent à des boutons radio.
  • value distinct : c'est ce que le serveur reçoit (livraison=express). Sans value, le navigateur envoie on.

Rien d'autre. Pas d'aria-checked, pas de role="radio" : l'élément natif porte déjà son rôle et son état, et les synchroniser à la main en JavaScript ajoute du code sans rien apporter. La première règle d'ARIA s'applique ici au pied de la lettre.

Bouton radio, case à cocher ou liste déroulante : lequel choisir

Le choix se fait sur la nature de la réponse, pas sur l'esthétique.

SituationComposant
Une seule réponse parmi 2 à 7 optionsBoutons radio
Plusieurs réponses possiblesCases à cocher
Une seule case : confirmation, consentementCase à cocher unique
Une seule réponse parmi une longue listeListe déroulante <select>
Options à comparer (prix, délais, descriptions)Boutons radio avec description

La règle du système de conception de Canada.ca tient en une phrase : « Utiliser des boutons radio lorsqu'une seule sélection peut être faite dans une liste. » Les cases à cocher servent quand « l'utilisateur peut faire une ou plusieurs sélections pour répondre à une question ».

Le <select> est le choix par défaut quand la liste dépasse la demi-douzaine d'options (pays, année de naissance). En dessous, il cache les options derrière un clic supplémentaire et oblige à les parcourir une à une : les boutons radio les montrent toutes d'un coup, et la personne compare avant de choisir.

Un bémol : les boutons radio ne conviennent pas pour une action. « Valider » ou « Passer à l'étape suivante » sont des boutons, pas des options.

Clavier et lecteur d'écran : ce que le navigateur fait déjà

Un groupe de boutons radio natifs se pilote entièrement au clavier, sans une ligne de JavaScript. Le pattern Radio Group de l'ARIA Authoring Practices Guide décrit le comportement attendu, et les navigateurs l'appliquent aux <input type="radio"> :

  • Tab entre dans le groupe et en sort. En entrant, le focus se pose sur le bouton coché, ou sur le premier bouton si aucun n'est coché.
  • Flèches bas et droite déplacent le focus sur le bouton suivant et le cochent dans le même geste. Flèches haut et gauche font l'inverse. Au bout du groupe, le focus repart au début.
  • Espace coche le bouton focalisé s'il ne l'est pas encore.

Un groupe de trois boutons radio ne consomme donc qu'un seul arrêt de tabulation. C'est ce que ressent la personne qui navigue au clavier : le groupe se comporte comme un seul champ à plusieurs valeurs.

Deux détails qui comptent en audit :

  • Les flèches cochent en se déplaçant. Si votre formulaire déclenche une action lourde au change (rechargement, calcul, envoi), une personne qui explore les options au clavier la déclenchera à chaque flèche. Le RGAA l'encadre par le critère 11.12 (modification ou suppression de données) et, pour les scripts, par le critère 7.3 : tout gestionnaire d'événement doit rester contrôlable au clavier sans effet non sollicité.
  • L'APG signale une nuance des navigateurs : avec Maj + Tab, si aucun bouton n'est coché, « certains navigateurs placent le focus sur le dernier bouton radio au lieu du premier ». Ce n'est pas un défaut de votre page.

Au lecteur d'écran, chaque bouton est annoncé avec son étiquette, son rôle (« bouton radio ») et son état (« coché » ou non) ; NVDA et JAWS ajoutent la position dans le groupe (« 1 sur 3 »). La légende du <fieldset> est lue à l'entrée dans le groupe. Vous pouvez l'entendre vous-même avec notre tutoriel NVDA et VoiceOver, ou en avoir un aperçu avec le simulateur de lecteur d'écran.

Personnaliser le bouton radio en CSS sans perdre le focus

C'est ici que la plupart des implémentations cassent. Le rendu natif d'un bouton radio dépend du système ; pour l'harmoniser avec une charte graphique, on masque l'input et on dessine un faux bouton. Le piège : masquer l'input d'une façon qui le retire du clavier ou de l'arbre d'accessibilité.

Ce qu'il ne faut pas faire :

CODE
/* Mauvais : l'input disparaît pour tout le monde,
   clavier et lecteurs d'écran compris */
input[type="radio"] {
  display: none;
}

Avec display: none (ou visibility: hidden), le bouton radio n'est plus focalisable et n'existe plus pour un lecteur d'écran. Le faux bouton dessiné en CSS, lui, n'a ni rôle ni état. Vous avez remplacé un composant accessible par une image.

Deux approches sûres existent.

Approche 1 : appearance: none et style direct de l'input

La plus simple en 2026. L'input reste un input, vous en redessinez l'apparence :

CODE
input[type="radio"] {
  appearance: none;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid #1a1a1a;
  border-radius: 50%;
  vertical-align: middle;
  cursor: pointer;
}
input[type="radio"]:checked {
  border-width: 0.4rem;        /* le disque intérieur */
}
input[type="radio"]:focus-visible {
  outline: 3px solid #1a1a1a;
  outline-offset: 2px;
}

MDN précise que l'implémentation d'appearance « varie beaucoup » dans les anciens navigateurs ; dans les navigateurs récents, les différences sont minimes. Le focus, le clavier et la restitution ne changent pas : vous n'avez touché qu'au dessin.

Approche 2 : masquage visuel et faux bouton sur l'étiquette

L'input est retiré de l'affichage mais reste dans la page, et le faux bouton se dessine sur un pseudo-élément du <label>. Le masquage se fait par découpe et positionnement, jamais par display: none :

CODE
input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
input[type="radio"] + label::before {
  content: "";
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid #1a1a1a;
  border-radius: 50%;
  margin-right: 0.5rem;
  vertical-align: middle;
}
input[type="radio"]:checked + label::before {
  background: radial-gradient(#1a1a1a 45%, transparent 50%);
}
input[type="radio"]:focus-visible + label::before {
  outline: 3px solid #1a1a1a;
  outline-offset: 2px;
}

La dernière règle est celle qu'oublient les tutoriels de personnalisation : l'input masqué reçoit toujours le focus, mais plus rien ne le montre. Sans un style :focus-visible reporté sur le faux bouton, la personne au clavier ne sait plus où elle est. C'est le critère RGAA 10.7 : « le style du focus natif du navigateur n'est pas supprimé ou dégradé », ou « un style du focus défini par l'auteur est visible ». Notre guide des indicateurs de focus détaille le contraste attendu.

Attention

Un faux bouton dessiné sur ::before n'a pas de contraste garanti. Le disque et sa bordure doivent atteindre 3:1 avec le fond (critère RGAA 3.3, composants d'interface). Un gris clair sur fond blanc passe visuellement en maquette et échoue à la mesure.

Dans les deux approches, agrandissez la zone cliquable : le <label> relié par for rend l'étiquette entière cliquable, et Canada.ca recommande des contrôles de 36 pixels pour le tactile, « plus faciles à sélectionner et à utiliser, en particulier sur un appareil mobile ».

Obligatoire, pré-coché, erreurs : les décisions qui se discutent

Trois décisions reviennent dans chaque revue de formulaire.

Faut-il pré-cocher une option ? MDN note qu'il est « assez rare de vouloir permettre l'envoi du formulaire sans qu'aucun bouton radio d'un groupe ne soit sélectionné », et suggère un checked par défaut. En pratique, la réponse dépend de la question. Pour un réglage avec une valeur courante (« Format : PDF »), pré-cochez. Pour une vraie question (« Souhaitez-vous recevoir la newsletter ? »), ne pré-cochez rien : un choix par défaut est un choix fait à la place de la personne, et le consentement en particulier ne se présume pas. Rendez alors le groupe obligatoire.

Comment rendre un groupe obligatoire ? Avec required sur un seul des boutons. MDN est explicite : « Si un bouton radio d'un groupe partageant le même nom possède l'attribut required, un bouton de ce groupe doit être sélectionné, même si ce n'est pas forcément celui qui porte l'attribut. » Côté RGAA, le critère 11.10 demande en plus une indication visible du caractère obligatoire avant la validation, située dans la légende ou dans un texte associé au groupe : un astérisque expliqué en tête de formulaire, ou la mention « (obligatoire) » dans la <legend>.

Comment signaler l'erreur ? Si rien n'est coché à l'envoi, le navigateur bloque la soumission et affiche son propre message. Si vous gérez la validation vous-même, le message doit être visible, nommer le groupe concerné, et le groupe reçoit aria-invalid="true" (test 11.10.3). Reliez le message au groupe par aria-describedby sur le <fieldset> pour qu'il soit lu avec la légende. Le guide des formulaires accessibles couvre la gestion d'erreurs dans son ensemble.

Si aucun bouton n'est coché et que le groupe n'est pas obligatoire, retenez enfin ce détail de MDN : « le groupe radio n'est pas inclus dans les données envoyées ». Votre serveur doit accepter l'absence de la clé, pas seulement une valeur vide.

Les six critères RGAA d'un groupe de boutons radio

Un groupe de boutons radio engage six critères du RGAA 4.1.2, plus deux conditionnels. Voici ce que chacun exige, et ce qu'un scan automatique en voit.

CritèreExigence pour des boutons radioScan
11.1Une étiquette par boutonDétecté
11.2Étiquette pertinenteManuel
11.5Boutons regroupés, si nécessairePartiel
11.6Légende sur le regroupementDétecté
11.7Légende pertinenteManuel
10.7Focus visible, natif ou restyléPartiel
11.10Obligatoire : indication visiblePartiel
7.3Script au changement : clavierManuel

Le test 11.5.1 mérite d'être lu en entier, parce qu'il nomme le composant. Les champs de même nature doivent être « regroupés dans une balise <fieldset> », ou « dans une balise possédant un attribut WAI-ARIA role="group" », ou, pour les champs « de type radio (<input type="radio">) ou balises possédant un attribut WAI-ARIA role="radio" », dans une balise avec role="radiogroup" ou role="group". Le <fieldset> natif reste la voie la plus simple, et la seule qui donne une légende sans ARIA.

Le « si nécessaire » du critère a un sens précis. Le glossaire du RGAA admet qu'un regroupement « devient inutile puisque les labels sont suffisamment pertinents » quand chaque étiquette porte déjà l'information du groupe. Trois boutons étiquetés « Standard », « Express » et « Retrait » ne disent pas de quoi il s'agit : le regroupement est nécessaire. Notre article sur fieldset et le RGAA détaille cette frontière, et le cas inverse de la sur-utilisation.

Ce que le scan de RGAA Checker fait sur ces critères, concrètement :

  • Un groupe de boutons radio de même name dans un <fieldset> ou un role="group" valide le 11.5 ; sans regroupement, il est signalé « à vérifier », avec le rappel que le critère ne l'impose que si les étiquettes ne suffisent pas.
  • Un <fieldset> sans <legend>, ou avec une légende vide, est une non-conformité 11.6 établie.
  • Un bouton radio sans étiquette, ou étiqueté par un simple placeholder, est une non-conformité 11.1 établie.
  • Une règle CSS qui supprime l'outline sans alternative détectable est signalée au titre du 10.7 ; la visibilité réelle et le contraste de l'indicateur ne se mesurent qu'au clavier ou par rendu.

La pertinence d'une étiquette ou d'une légende, elle, ne se détecte pas. « Oui » et « Non » sont des étiquettes parfaites sous la légende « Acceptez-vous les conditions ? », et inutilisables sans elle.

Conclusion

Un bouton radio HTML accessible, c'est un <input type="radio"> avec un name partagé, un <label for> par bouton, un <fieldset> et sa <legend> pour poser la question, required sur un bouton si le choix est obligatoire, et un :focus-visible reporté sur ce que vous avez redessiné. Le navigateur fournit le clavier, le rôle et l'état ; votre travail consiste à ne pas les lui retirer.

Pour savoir où en sont vos formulaires, un scan de la page liste déjà les boutons sans étiquette, les groupes sans légende et les suppressions de focus non compensées. Il vous reste ensuite à relire les étiquettes et les légendes avec la question en tête : une personne qui n'entend que ces mots comprend-elle ce qu'on lui demande ?

Questions fréquentes

Quelle est la différence entre un bouton radio et une case à cocher ?

Un groupe de boutons radio n'accepte qu'une seule réponse : cocher un bouton décoche l'autre. Les cases à cocher sont indépendantes, chacune peut être cochée ou non. Utilisez des boutons radio quand la question appelle une seule réponse parmi plusieurs, des cases à cocher quand plusieurs réponses sont possibles, et une case unique pour une confirmation.

Les boutons radio doivent-ils être affichés horizontalement ou verticalement ?

Verticalement par défaut : chaque option sur sa ligne, étiquette à droite du bouton. C'est la disposition la plus lisible au zoom 200 %, sur mobile et au lecteur d'écran. L'alignement horizontal reste acceptable pour deux ou trois options courtes (Oui / Non), à condition que l'étiquette reste visuellement rattachée à son bouton et que la zone cliquable couvre le texte.

Faut-il un fieldset pour chaque groupe de boutons radio ?

Dans la plupart des cas, oui : la question posée (« Mode de livraison ») n'est portée par aucune étiquette individuelle, et le RGAA demande alors un regroupement avec légende (critères 11.5 et 11.6). Le regroupement n'est pas exigé seulement si chaque étiquette suffit à elle seule à comprendre le choix, ce qui est rare pour des options courtes comme « Standard » ou « Express ».

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.