Cinq étoiles côte à côte, une astuce CSS avec des label inversés en ordre visuel et la pseudo-classe :hover pour colorer les étoiles précédentes au survol : ce motif de notation, très répandu sur les fiches produit et les pages d’avis, fonctionne parfaitement à la souris. Il ne fonctionne pas du tout au clavier, puisque rien dans cette construction ne reçoit le focus ni ne réagit aux flèches.
Ce billet propose un remplacement complet du widget par un groupe de boutons radio natifs, habillé en étoiles par le CSS, avec une variante permettant la demi-étoile pour les moyennes calculées.
Le problème du widget CSS pur
La construction classique enchaîne des paires input[type=radio] masquées visuellement et des label stylés en étoiles, disposés en ordre inversé dans le DOM pour que le sélecteur ~ permette de colorer les étoiles précédentes au survol. Le problème ne vient pas de l’input radio lui-même, qui reste nativement accessible, mais des variantes qui remplacent l’input par un simple <span> cliquable avec un gestionnaire onclick en JavaScript : dans ce cas, aucune touche Tab n’atteint le composant et aucune touche flèche ne change la note.

Le remplacement en groupe de boutons radio
La solution la plus robuste consiste à revenir à un vrai groupe de boutons radio, avec un fieldset et une legend qui décrivent la question posée :
<fieldset>
<legend>Votre note pour ce produit</legend>
<div class="etoiles">
<input type="radio" id="note-5" name="note" value="5">
<label for="note-5">5 étoiles</label>
<input type="radio" id="note-4" name="note" value="4">
<label for="note-4">4 étoiles</label>
<input type="radio" id="note-3" name="note" value="3">
<label for="note-3">3 étoiles</label>
<input type="radio" id="note-2" name="note" value="2">
<label for="note-2">2 étoiles</label>
<input type="radio" id="note-1" name="note" value="1">
<label for="note-1">1 étoile</label>
</div>
</fieldset>
Chaque label reçoit un texte complet (« 5 étoiles », pas juste une icône), masqué visuellement au profit d’un fond d’étoile en CSS mais lu intégralement par un lecteur d’écran. Le groupe de boutons radio hérite gratuitement du comportement clavier natif : les flèches haut et bas déplacent la sélection dans le groupe, la barre d’espace confirme un choix, et le focus reste parfaitement visible tant qu’aucun outline: none ne vient l’effacer.
Masquer l’input sans le retirer du clavier
La technique correcte pour masquer visuellement l’input tout en le gardant accessible au clavier repose sur un positionnement hors écran, jamais sur display: none ou visibility: hidden qui retirent l’élément de l’ordre de tabulation :
.etoiles input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
La variante demi-étoile
Pour afficher une moyenne calculée avec une demi-étoile, deux approches coexistent. La première, en lecture seule, se contente d’un texte explicite : <p>Note moyenne : 4,5 sur 5</p> accompagné d’un habillage visuel en étoiles décoratives, sans piège d’interaction puisqu’il n’y a rien à sélectionner. La seconde, pour un formulaire de notation qui autorise réellement la demi-étoile, double le nombre de boutons radio avec un pas de 0,5 :
- Dix valeurs possibles (0,5 à 5) au lieu de cinq, chacune avec son propre
labelexplicite. - Un affichage visuel qui superpose deux étoiles en CSS (fond et remplissage partiel) sans jamais reposer l’interaction dessus.
- Un test au clavier systématique après implémentation : dix pressions de flèche doivent parcourir les dix valeurs sans saut ni blocage.
Sur les fiches produit que j’ai reprises, l’ancien widget en
<span>cliquables restait souvent invisible aux audits automatisés, qui ne remontent pas toujours l’absence d’input sous-jacent : le test au clavier manuel reste irremplaçable ici.
En résumé
Un groupe de boutons radio natif, habillé en étoiles par le CSS plutôt que remplacé par des éléments cliquables non sémantiques, redonne au widget de notation tout le comportement clavier que le navigateur fournit gratuitement. La demi-étoile s’obtient en doublant les valeurs plutôt qu’en bricolant une détection de position de clic, ce qui garde le composant utilisable sans script tiers.