Dernière mise à jour le 01/11/2025
Les boutons radio semblent simples. Un clic, un choix. Cependant, ils constituent aussi l'un des moyens les plus faciles de compromettre l'expérience utilisateur et l'accessibilité. Masquez la saisie native, ignorez fieldset et legend, ou validez de manière trop agressive et vous expédierez un formulaire qui déroute les utilisateurs du clavier, frustre les lecteurs d'écran et fait perdre des conversions.
Ce guide vous explique comment créer des radios performantes en 2025 : rapides à choisir, faciles à analyser, entièrement accessibles et mesurables. Nous commencerons par une base sémantique claire, puis nous y ajouterons du CSS moderne pour que les radios ressemblent à des cartes ou à des contrôles segmentés sans perturber le fonctionnement du clavier ou du lecteur d'écran.
Vous découvrirez des modèles de validation pratiques et efficaces, des mises en page réactives pour atteindre des objectifs ambitieux sur mobile et des extraits de code à coller dans n'importe quelle pile. Nous aborderons également les analyses, les idées A/B et la configuration de Formspree pour une capture immédiate des résultats.
À qui s'adresse-t-il : aux concepteurs, aux développeurs front-end et aux spécialistes du marketing qui se soucient des deux accessibilité, et ConversionAucun framework lourd requis, juste du HTML solide, du CSS ciblé et une petite amélioration progressive.
À la fin, vous saurez :
- Lorsque les radios battent, sélectionnez, cochez ou basculez
- Comment créer un groupe radio accessible (
fieldset,legend, étiquetage, mise au point) - Comment personnaliser les radios « carte » sans masquer l'entrée
- Comment valider avec l'API HTML Constraint (et bien transmettre le message)
- Comment connecter des radios à Formspree et mesurer les sélections
TL; DR
- Utilisez le radios pour exactement UN choix dans un ensemble bien défini (idéalement 2 à 6 options) cela devrait être visible immédiatement.
- Enveloppez toujours les radios dans
<fieldset>+<legend>, donne-leur le mêmename, et faites de chaque option une cliquable<label>. - Ne masquez pas les entrées avec
display:none; garde-les focalisable (par exemple,opacity:0; position:absolute; inset:0) donc Tabulation/Flèches/Espace . - Fixez-vous de grandes cibles : au moins 44 × 44px, avec une bague de mise au point c'est clairement visible.
- Valider avec le API de contrainte HTML (
required,reportValidity()), puis améliorez les messages, et non l’inverse. - Si vous avez beaucoup d'options ou si elles ne vous sont pas familières, ajoutez texte d'aide sous chaque étiquette et considérez recherche ou regroupement à la place.
- Sur mobile, empilez une colonne; sur les écrans larges, 2–3 colonnes via la grille CSS.
- Mesure : piste impressions, sélection et soumission, et test A/B ordre d'option et copier.
- Pour Formspree, conservez POST natif en guise de solution de secours ; ajouter rapporter seulement comme une amélioration progressive.
Quand utiliser les radios (arbre de décision)
Q1. L'utilisateur choisit-il exactement une option ?
- Non → Utilisez le checkboxes (0–plusieurs) ou un basculer (binaire) ou repenser la tâche.
- Oui → Aller à la Q2.
Q2. Les options doivent-elles être visibles d'un seul coup d'œil ?
- Oui → Utilisez le radios (idéal pour la numérisation et le choix rapide).
- Non (l'espace est restreint / beaucoup d'options) → Considérez un Sélectionner (menu déroulant). Si les choix sont critiques et méritent d'être comparés, privilégiez les radios et repensez la mise en page.
Q3. Combien d'options ?
- 2-6 → Les radios sont idéales.
- 7-10 → Les radios peuvent toujours fonctionner avec un groupement ou un deux colonnes grille ; sinon, utilisez une liste de sélection ou une liste consultable.
- > 10 → Utiliser un sélectionner avec la recherche étape ce qui réduit d'abord l'ensemble.
Q4. Les options sont-elles mutuellement exclusives ?
- Oui → radios.
- Aucune / les combinaisons n'ont de sens → Cases à cocher.
Q5. Ce choix entraînera-t-il des questions complémentaires différentes ?
- Oui → Les radios fonctionnent toujours. Voir les suites. conditionnellement, mais gardez les champs cachés hors de l'ordre des onglets et le flux SR jusqu'à ce qu'il soit révélé.
- Non → Groupe radio simple.
Q6. Familier ou descriptif ?
- Étiquettes familières (par exemple, « Petit / Moyen / Grand ») → des étiquettes courtes suffisent.
- Inconnu ou à enjeux élevés (par exemple, « SEO technique vs. SEO de contenu ») → ajouter microcopie (description sur une ligne) sous chaque étiquette.
Q7. Mobile d'abord ?
- Oui (toujours) → Utilisation pile à une colonne avec un espacement généreux et un texte de 16 à 18 px. Promouvez le le plus choisi option vers le haut.
Exemples pratiques
- Votre formulaire: « Quel service ? » → Les radios sont plus efficaces qu'une sélection, car les utilisateurs doivent comparer les options côte à côte. Conservez trois fiches (création de liens, référencement, développement web) avec un sous-texte court.
- Vitesse de livraison: 3–4 vitesses mutuellement exclusives → Radios (avec deltas de prix en sous-texte).
- Fréquence de la newsletter: Quotidien/Hebdomadaire/Mensuel → Radios.
- Intérêts (possibilité d'en choisir plusieurs) → Pas de radios ; utilisez des cases à cocher.
Anatomie d'un groupe radio approprié
Copier-coller la ligne de base sémantique (pas encore de CSS sophistiqué)
<form id="quote-form" action="/fr/submit" method="post">
<fieldset id="service-group">
<legend>Which service do you need?</legend>
<p id="service-hint">Pick one option that best matches your goal.</p>
<div class="field">
<label>
<input type="radio" name="service" id="svc-link" value="link-building" required>
<span>Link Building</span>
</label>
<label>
<input type="radio" name="service" id="svc-seo" value="seo">
<span>Search Engine Optimization</span>
</label>
<label>
<input type="radio" name="service" id="svc-web" value="web-dev">
<span>Web Design / Development</span>
</label>
</div>
<p id="service-error" class="error" role="alert" hidden>
Please choose a service to continue.
</p>
</fieldset>
<button type="submit">Continue</button>
</form>
Pourquoi chaque partie est importante
<fieldset>+<legend>donne au groupe un nom pour les lecteurs d'écran et définit un contexte pour tout le monde.- Béton
namerend les entrées mutuellement exclusives. - Cliquable
<label>autour de chaque entrée vous offre une immense zone de tapotement/clic (44 px+). requiredà la première radio permet au navigateur de valider le groupe de manière native.- Texte d'aide (
service-hint) et texte d'erreur (service-error) sont séparés afin que vous puissiez afficher/masquer les erreurs sans déplacer les éléments. - Aucun rôle ARIA n'est nécessaire. Les radios natives exposent déjà la sémantique correcte.
CSS minimal pour la convivialité (aspect natif, grandes cibles, mise au point claire)
/* Layout & spacing */
.field label {
display: flex;
align-items: center;
gap: .6rem;
padding: .65rem .8rem; /* big tap target */
border-radius: .6rem;
cursor: pointer;
}
/* Keyboard focus: highlight the whole label when the input is focused */
.field label:has(input:focus-visible) {
outline: 3px solid #3b82f6;
outline-offset: 2px;
}
/* Selected state (optional, native dot still shows) */
.field label:has(input:checked) {
background: #f1f5ff;
}
/* Error text */
.error { color: #b91c1c; margin-top: .5rem; font-size: .9rem; }
Astuce:
:has()Vous obtenez un « anneau de mise au point sur toute la carte » sans masquer l'entrée réelle. Si un navigateur plus ancien ne le prend pas en charge, les utilisateurs bénéficient toujours de l'anneau de mise au point natif sur la radio elle-même.
Validation douce et accessible (utilise d'abord le navigateur)
Laissez le navigateur gérer la partie « vous devez en choisir un », puis affichez votre erreur en ligne si nécessaire.
const form = document.getElementById('quote-form');
const group = document.getElementById('service-group');
const error = document.getEementById('service-error');
form.addEventListener('submit', (e) => {
// Trigger native validation UI (works because the first radio has `required`)
if (!form.reportValidity()) {
// Optional: surface a friendly inline error too
error.hidden = false;
group.setAttribute('aria-invalid', 'true');
e.preventDefault();
} else {
error.hidden = true;
group.removeAttribute('aria-invalid');
}
});
Comportement auquel vous devez vous attendre (et tester) :
- Clavier: Appuyez une fois sur la touche Tab pour entrer dans le groupe ; utilisez Touches directionnelles se déplacer; évenementiels sélectionne.
- Lecteurs d'écran : La légende est lue comme l'étiquette du groupe ; chaque option est annoncée avec l'état « sélectionné/non sélectionné ».
- Touchez: Appuyer sur l'étiquette permet de basculer la radio (grâce à l'habillage de l'étiquette).
Radios « cartes » personnalisées (sans compromettre l'accessibilité)
Copier-coller HTML
<form id="plan" action="/fr/submit" method="post">
<fieldset>
<legend>Choose a plan</legend>
<p id="plan-hint">Pick one option. You can change anytime.</p>
<div class="card-group" role="group" aria-describedby="plan-hint">
<label class="card">
<input type="radio" name="plan" value="starter" required>
<span class="card-title">Starter</span>
<span class="card-sub">Good for small sites</span>
</label>
<label class="card">
<input type="radio" name="plan" value="growth">
<span class="card-title">Growth</span>
<span class="card-sub">Most popular</span>
</label>
<label class="card">
<input type="radio" name="plan" value="pro">
<span class="card-title">Pro</span>
<span class="card-sub">Advanced features</span>
</label>
</div>
<p id="plan-error" class="error" role="alert" hidden>Select one to continue.</p>
</fieldset>
</form>
CSS minimal et robuste
:root{
--brand:#0b5cff; --ring: rgba(11,92,255,.35);
--border:#e5e7eb; --bg:#fff; --muted:#6b7280;
--radius:14px;
}
/* Grid */
.card-group{
display:grid; gap:16px;
grid-template-columns: repeat(3, minmax(0,1fr));
}
@media (max-width:900px){ .card-group{ grid-template-columns:1fr 1fr; } }
@media (max-width:640px){ .card-group{ grid-template-columns:1fr; } }
/* Card label */
.card{
position:relative;
display:grid; place-items:center; text-align:center;
gap:6px; padding:18px;
border:1px solid var(--border); border-radius:var(--radius);
background:var(--bg);
cursor:pointer; user-select:none;
transition: box-shadow .15s, border-color .15s, transform .02s;
}
.card:hover{ box-shadow:0 8px 20px rgba(0,0,0,.06); }
/* Keep the native input focusable: no display:none */
.card input{
position:absolute; inset:0; opacity:0;
}
/* Keyboard focus ring on the whole card */
.card:has(input:focus-visible){
box-shadow:0 0 0 3px var(--ring);
border-color: var(--brand);
}
/* Selected state */
.card:has(input:checked){
border-color: var(--brand);
box-shadow:0 0 0 3px var(--ring);
}
/* Content */
.card-title{ font-weight:700; color:#111; }
.card-sub{ font-size:14px; color:var(--muted); }
/* Error text */
.error{ color:#b91c1c; margin-top:.5rem; font-size:.9rem; }
Notes sur le comportement (pourquoi ce modèle fonctionne)
- Non
display:nonesur l'entrée → les utilisateurs du clavier peuvent appuyer sur Tab ; Touches directionnelles déplacer la sélection; évenementiels sélectionne. - La carte entière est cliquable car l'entrée est à l'intérieur du
label. :has()laissons-nous styliser concentration et vérifié états sur la carte parent sans hacks ARIA.- La grille réactive donne 1–3 colonnes automatiquement ; les cibles tactiles restent grandes.
Facultatif : validation douce (natif d'abord)
const form = document.getElementById('plan');
const err = document.getElementById('plan-error');
form.addEventListener('submit', (e) => {
if (!form.reportValidity()) { // uses the `required` on first radio
err.hidden = false;
e.preventDefault();
} else {
err.hidden = true;
}
});Stratégies de validation qui ne sont pas ennuyeuses
Une validation efficace est invisible jusqu'à ce qu'elle soit nécessaire. Voici une approche spécifique à la radio, rapide, accessible et apaisante pour les utilisateurs.
Principes
- Natif d'abord. Laissez HTML gérer « un doit être sélectionné ».
- Reporter les erreurs. Ne criez pas sur les utilisateurs avant qu'ils essaient de continuer.
- Indiquez le problème. Annoncez les erreurs, déplacez le focus intelligemment et gardez le texte court.
- Clair sur le changement. Dès qu'un choix valide est fait, supprimez l'erreur.
Ligne de base (native uniquement)
HTML seul peut appliquer la règle avec required sur une radio du groupe.
<fieldset id="svc" aria-describedby="svc-hint">
<legend>Which service do you need?</legend>
<p id="svc-hint">Pick one option.</p>
<label>
<input type="radio" name="service" value="link" required>
<span>Link Building</span>
</label>
<label>
<input type="radio" name="service" value="seo">
<span>Search Engine Optimization</span>
</label>
<label>
<input type="radio" name="service" value="web">
<span>Web Design / Development</span>
</label>
</fieldset>
Cela vous donne déjà : les flèches du clavier, la barre d'espace pour sélectionner et la validation au niveau du navigateur.
Erreurs en ligne conviviales (amélioration progressive)
Désactivez la fenêtre contextuelle du navigateur et gérez la messagerie en ligne afin qu'elle corresponde à votre conception.
HTML (ajouter une zone d'erreur)
<form id="quote" novalidate>
<!-- fieldset from above -->
<p id="svc-err" class="error" role="alert" hidden>Please choose one option.</p>
<button type="submit">Continue</button>
</form>
CSS (style simple)
.error { color:#b91c1c; margin-top:.5rem; font-size:.9rem; }
JS (différer les erreurs, annoncer correctement)
const form = document.getElementById('quote');
const group = document.getElementById('svc');
const err = document.getElementById('svc-err');
const radios = form.querySelectorAll('input[name="service"]');
let attempted = false; // show errors only after user tries to continue
function hasSelection() {
return [...radios].some(r => r.checked);
}
function showError(msg='Please choose one option.') {
err.textContent = msg;
err.hidden = false;
group.setAttribute('aria-invalid', 'true');
// Append error to describedby so SRs announce it next
group.setAttribute('aria-describedby', 'svc-hint svc-err');
}
function clearError() {
err.hidden = true;
group.removeAttribute('aria-invalid');
group.setAttribute('aria-describedby', 'svc-hint');
}
form.addEventListener('submit', (e) => {
attempted = true;
if (!hasSelection()) {
e.preventDefault();
showError();
// Bring the group into view and place focus on the first radio
radios[0].focus();
group.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
clearError();
}
});
// Don’t nag early; clear once they pick something
radios.forEach(r => {
r.addEventListener('change', () => {
if (attempted) clearError();
});
});
Pourquoi ça marche
- Aucune infobulle dans le navigateur ; les utilisateurs voient des erreurs en ligne à côté du groupe.
- Les lecteurs d'écran entendent le légende, puis le erreur, Grâce à
aria-describedby. - Les erreurs n'apparaissent que après une tentative de soumission (ou lors d'un retour pour corriger).
Validation douce par étape (formulaires à plusieurs étapes)
Si vous cliquez sur un bouton « Suivant », validez uniquement cette étape.
document.getElementById('nextBtn').addEventListener('click', () => {
if (!hasSelection()) {
showError();
} else {
clearError();
// advance to next step...
}
});
Messages personnalisés avec l'API Constraint (facultatif)
Si vous préférez conserver le modèle de validité du navigateur, définissez un message personnalisé sur le premier radio dans le groupe.
const firstRadio = radios[0];
form.addEventListener('submit', (e) => {
if (!hasSelection()) {
firstRadio.setCustomValidity('Please choose one option.');
// Triggers native UI; or call form.reportValidity()
e.preventDefault();
form.reportValidity();
} else {
firstRadio.setCustomValidity('');
}
});
Utilisez cette option lorsque vous souhaitez la « bulle » native ainsi que votre propre texte, sinon, restez fidèle à la méthode en ligne ci-dessus pour une interface utilisateur cohérente.
Résumé des erreurs (pour les formulaires longs)
Si votre formulaire est long, ajoutez un résumé principal qui fait le lien avec la zone problématique.
<div id="error-summary" class="error" role="alert" hidden>
There’s a problem: <a href="#svc">Choose a service</a>.
</div>
function showSummary() {
const sum = document.getElementById('error-summary');
sum.hidden = false;
sum.querySelector('a').focus(); // SRs announce the alert
}
Solutions de secours côté serveur et réseau
La validation côté client améliore l'expérience utilisateur, mais toujours Validez également sur le serveur (ou par votre service de soumission). En cas d'échec de la soumission, renvoyez la page avec :
- La précédente la sélection a persisté et
- Le même erreur en ligne à côté du groupe.
Intégration Formspree (amélioration progressive)
Faites fonctionner les radios même si JavaScript échoue, puis améliorez-les pour un remerciement plus fluide et de meilleures analyses.
HTML natif (fonctionne toujours)
<form
id="quote"
action="https://formspree.io/f/your_form_id"
method="POST"
>
<fieldset>
<legend>Which service do you need?</legend>
<p id="svc-hint">Pick one option.</p>
<label class="card">
<input type="radio" name="service" value="link-building" required>
<span class="card-title">Link Building</span>
<span class="card-sub">Earn high-quality backlinks.</span>
</label>
<label class="card">
<input type="radio" name="service" value="seo">
<span class="card-title">Search Engine Optimization</span>
<span class="card-sub">Boost rankings & traffic.</span>
</label>
<label class="card">
<input type="radio" name="service" value="web-dev">
<span class="card-title">Web Design / Development</span>
<span class="card-sub">Fast, mobile-first sites.</span>
</label>
</fieldset>
<label>
<span>Full Name</span>
<input type="text" name="name" autocomplete="name" required>
</label>
<label>
<span>Email</span>
<input type="email" name="email" autocomplete="email" required>
</label>
<!-- Optional: subject line in your inbox -->
<input type="hidden" name="_subject" value="New lead from Contact form">
<!-- Simple honeypot -->
<input type="text" name="company" tabindex="-1" autocomplete="off" style="position:absolute; left:-9999px" aria-hidden="true">
<button type="submit">Get My Free Proposal</button>
<p id="form-error" class="error" role="alert" hidden>Something went wrong. Please try again.</p>
<p id="form-success" class="success" role="status" hidden>Thanks! We’ll be in touch shortly.</p>
</form>
Ce que cela vous donne :
- Fonctionne avec JS désactivé (bon pour le référencement, la fiabilité).
- Le navigateur gère « il faut en sélectionner un » (
requiredsur la première radio). - Formspree reçoit
service,name,emailet_subject.
Amélioration JS (belle UX, meilleur suivi)
Intercepter la soumission, valider, envoyer avec fetchet affichez un remerciement en ligne (ou une redirection).
<script>
(function(){
const form = document.getElementById('quote');
const okMsg = document.getElementById('form-success');
const errMsg = document.getElementById('form-error');
const submitBtn = form.querySelector('button[type="submit"]');
// Optional: capture UTM + page context
function appendMeta(fd){
const url = new URL(location.href);
['utm_source','utm_medium','utm_campaign','utm_term','utm_content'].forEach(k=>{
const v = url.searchParams.get(k);
if(v) fd.append(k, v);
});
fd.append('page_url', location.href);
if (document.referrer) fd.append('referrer', document.referrer);
}
form.addEventListener('submit', async (e) => {
// Keep native fallback if JS fails
e.preventDefault();
// Use native validity (includes radio required)
if (!form.reportValidity()) return;
const fd = new FormData(form);
// Don’t send honeypot if filled (treat as spam)
if (fd.get('company')) return; // silently drop bots
appendMeta(fd);
submitBtn.disabled = true;
const original = submitBtn.textContent;
submitBtn.textContent = 'Submitting…';
okMsg.hidden = true; errMsg.hidden = true;
try{
const res = await fetch(form.action, {
method: 'POST',
body: fd,
headers: { 'Accept': 'application/json' }
});
if(res.ok){
form.reset();
okMsg.hidden = false;
// Optional: fire analytics
if (window.gtag) gtag('event','generate_lead',{method:'formspree', service: fd.get('service')});
// Optional: redirect after success
// setTimeout(()=> location.href = '/thank-you/', 1200);
} else {
errMsg.hidden = false;
}
} catch {
errMsg.hidden = false;
} finally {
submitBtn.disabled = false;
submitBtn.textContent = original;
}
});
})();
</script>
Remarques
- complet »
Accept: application/jsonAinsi, Formspree renvoie du JSON et vous pouvez décider clairement du succès/échec. - Utilisez le
form.reportValidity()Ainsi, le navigateur applique la sélection radio et le format de l'e-mail avant l'envoi. - Gardez le natif
action/methoddonc le formulaire fonctionne toujours sans JS.
Contrôle du spam sans nuire aux conversions
- Pot De Miel (inclus ci-dessus) attrape de nombreux robots sans aucune friction.
- Timing: ajouter un caché
started_atlorsque la page se charge ; ignorez les soumissions plus rapides que, disons, 2 secondes. - Signaux de contenu: si vous voyez des valeurs indésirables répétées, filtrez-les dans vos règles Formspree ou post-traitement.
<input type="hidden" name="started_at" id="started_at">
<script>
document.getElementById('started_at').value = Date.now();
</script>
// in submit handler before sending:
const started = Number(fd.get('started_at'));
if (Date.now() - started < 2000) return; // looks like a bot
Lignes d'objet reflétant le choix de la radio (boîte de réception propre)
Vous pouvez personnaliser _subject à partir de la radio sélectionnée :
const service = fd.get('service'); // e.g., "seo"
fd.set('_subject', `New ${service} lead from Contact form`);
Les pièges courants à éviter
- Cacher les radios avec
display:none. Utilisez leopacity:0; position:absolute; inset:0à l'intérieur de l'étiquette pour que les claviers fonctionnent toujours. - Oubli
fieldset/legend. Les lecteurs d’écran s’appuient sur eux pour le contexte. - Sur-validation précoce. Afficher les erreurs après la soumission/Suivant, puis effacer dès que l'utilisateur corrige le groupe.
- Ne pas saisir le contexte. Ajouter
page_url,referrer, et les champs UTM, vous vous remercierez plus tard.
Internationalisation et inclusivité
Langue, étiquettes et ton
- Utilisez la page
lang(et changer de page selon les paramètres régionaux) :<html lang="en"> … </html> - Gardez les étiquettes courtes et descriptif, évitez le jargon spécifique à une culture.
- Préférez fil neutre formulation (« Choisissez un plan ») plutôt que des hypothèses (« Quel est votre budget ? »).
- Localiser erreurs et texte d'aide avec un dictionnaire simple indexé par
document.documentElement.lang.
const i18n = {
en: { choose: 'Please choose one option.' },
es: { choose: 'Elige una opción, por favor.' },
ar: { choose: 'يُرجى اختيار خيار واحد.' }
};
const t = (k) => (i18n[document.documentElement.lang] || i18n.en)[k];
errorEl.textContent = t('choose');
Étiquettes longues et options multilignes
- Laisser les étiquettes envelopper; ne tronquez jamais les mots cruciaux.
- Gardez la zone de frappe grande même lorsque le texte s'enroule : utilisez un remplissage sur le étiquette, pas seulement l'entrée.
.card { inline-size: 100%; padding: 16px; }
.card .card-title { font-weight: 700; line-height: 1.25; }
.card .card-sub { font-size: 14px; line-height: 1.4; }
Prise en charge de droite à gauche (RTL)
Utiliser CSS propriétés logiques et :dir() vous n'avez donc pas besoin de styles séparés.
/* Spacing that flips in RTL automatically */
.card { padding-inline: 16px; padding-block: 16px; }
/* Focus ring that respects direction */
.card:has(input:focus-visible) {
outline: 3px solid var(--ring);
outline-offset: 2px;
}
/* Optional: direction-specific tweaks */
:dir(rtl) .card-group { direction: rtl; }
Accessibilité au-delà de l'ARIA
- Qu'on Assure contraste ≥ 4.5:1 pour les étiquettes et les états de focus.
- Cibles tactiles ≥ 44 × 44 px (rembourrage plutôt que taille de police).
- Ne communiquez pas l'état uniquement par la couleur ; utilisez bordures, icônes ou texte.
Liste de contrôle des tests (version imprimable)
- Sémantique:Les radios sont à l'intérieur
fieldsetavec un visiblelegend. - Clavier: Tab entre dans le groupe ; flèches se déplacer; évenementiels sélectionne.
- Lecteurs d'écran: Légende annoncée ; chaque option indique sélectionnée/non sélectionnée.
- Focus:Anneau visible sur le carte entière (et sur l'entrée native).
- Zone touchée: L'étiquette entière est cliquable ; fonctionne sur mobile.
- Validation : Aucune erreur jusqu'à la soumission/Suivant ; les erreurs disparaissent lors du changement.
- Copier:Les options sont courtes ; un texte d'aide clarifie les différences.
- Mise En Page: 1 colonne sur les téléphones ; 2–3 sur les ordinateurs de bureau ; pas de débordement.
- i18n: Erreurs/étiquettes localisées ; le texte long s'enroule correctement ; RTL OK.
- Aperçu général des Matières enseignées:Événements pour impression, sélection, soumission.
- Performances: Pas de bibliothèques d'interface utilisateur lourdes ; CSS minimal ; pas de changement de mise en page.
Performances et maintenabilité
- Favoriser entrées natives + CSS léger ; évitez de remplacer les radios par des divs.
- Gardez le CSS modulaire (propriétés personnalisées pour les couleurs, le rayon, l'anneau).
- Éviter les
display:nonesur les intrants ; utilisationopacity:0; position:absolute; inset:0afin que l'accessibilité reste intacte. - Reportez le JS non critique ; conservez les améliorations sous ~2–5 Ko lorsque cela est possible.
- Utilisez le requêtes de conteneur (lorsqu'il est disponible) au lieu de points d'arrêt multimédias complexes pour les formulaires intégrés.
@container (min-width: 680px) {
.card-group { grid-template-columns: 1fr 1fr 1fr; }
}
Analyse et expérimentation
Suivre l'intention et les points de friction :
// Fire when the radio group becomes visible
gtag?.('event','form_step_view', { step: 1, form: 'contact' });
// Fire on selection
document.querySelectorAll('input[name="service"]').forEach(r => {
r.addEventListener('change', () => {
gtag?.('event','radio_select', { group: 'service', value: r.value });
});
});
// Fire on successful submit
gtag?.('event','generate_lead', { form: 'contact', service: form.service.value });
Test A/B :
- Ordre d'option (le plus choisi en premier)
- Copie du CTA (« Obtenez ma proposition gratuite » contre « Obtenez mon devis »)
- Texte d'aide du brand
Anti-modèles (à ne pas faire)
- Masquer les entrées avec
display:none(casse le clavier et les SR). - Manquant
fieldset/legend. - Petites zones touchées (seul le petit point est cliquable).
- État de sélection de couleur uniquement ; focus invisible.
- Validation avant l'utilisateur essaie de continuer.
- Regroupez 10 à 20 options dans des radios ; utilisez plutôt une sélection consultable.
Liste de contrôle finale
fieldset+legendreprésentent- Béton
name,uniquevalues - Les étiquettes enveloppent les entrées ; la carte entière est cliquable
- Bague de mise au point clairement visible
requiredà la première radio ; légères erreurs en ligne- Grille réactive de 1 à 3 colonnes, grandes cibles tactiles
- Chaînes localisées ; RTL sécurisé via des propriétés logiques
- Analyses sur la visualisation/sélection/soumission
- La validation côté serveur reflète les règles du client
Ressources
Spécifications et modèles de création
- Norme HTML –
<input type="radio">: sémantique native, regroupement parname, comportement de forme. - Pratiques de création WAI-ARIA 1.2 – Groupe Radio: comportement attendu du clavier (Tabulation dans le groupe, flèche se déplacer, évenementiels (à sélectionner), modèles d'étiquetage.
- WCAG 2.2 essentiels pour les radios :
- 2.1.1 Clavier (tout est exploitable via le clavier)
- 2.4.7 Focus visible (mise au point clairement visible)
- 2.5.5 Taille de la cible (≥44×44px recommandé)
- 1.4.3 Contraste (minimum)
Guides de référence
- mdn: Entrée radio,
label,fieldset/legend, validation de formulaire (API de validation de contrainte). - Système de conception GOV.UK – Radios: d'excellents conseils éprouvés sur la formulation et les erreurs.
- Composantes inclusives (Heydon Pickering):modèles d'approche pratiques et critiques.
Consultez notre dernier article de blog sur Comment trouver des opportunités de publication d'articles invités sur Ahrefs (Guide étape par étape)
Tests et outillage
- Outils de développement axe (extension de navigateur) – vérifications automatisées des accès.
- Insights sur l'accessibilité – tests guidés rapides.
- Lighthouse – signaux de performance + accessibilité.
- Vérificateur de contraste WebAIM – vérifier le contraste des couleurs.
- Lecteurs d'écran pour vérifier ponctuellement le comportement réel :
- NVDA (Les fenêtres), MÂCHOIRES (Les fenêtres), VoiceOver (macOS/iOS), Réagissez (Android).
- Passage manuel du clavier: Tab dans le groupe → Les flèches se déplacent → L'espace sélectionne → la bague de mise au point reste évidente.
Consultez les revue de Notions Marketing
FAQ (courtes et pratiques)
Viser 2-6Si vous en avez 7 à 10, regroupez-les ou utilisez une grille à deux colonnes. Au-delà de 10, passez à une grille à deux colonnes. sélectionner (avec recherche) ou repenser pour affiner d'abord les choix.
Seulement s'il y a un majorité claire par défaut et cela ne biaisera pas les données. La présélection accélère la complétion, mais peut réduire les choix délibérés. En cas de doute, ne pas présélectionner.
Les radios: choix unique, les options doivent être visible côte à côte.
Choisir:choix unique lorsque l'espace est restreint ou que les options sont nombreuses.
Les cases à cocher: 0–plusieurs choix (non mutuellement exclusifs).
Oui. Gardez le entrée native focalisable (ne pas utiliser display:none). Placez l'entrée à l'intérieur de l' <label>, réglez-le sur opacity:0; position:absolute; inset:0;, et stylisez l'étiquette. Utilisez :has(input:focus-visible) et :has(input:checked) pour piloter les états ciblés/sélectionnés.
Utilisez d'abord HTML : mettez required à la première radio et appelle form.reportValidity() (ou laissez le navigateur gérer l'envoi). Pour une interface utilisateur plus agréable, affichez un court erreur en ligne près du groupe après la première tentative, et clair sur le changement.
role="radiogroup"?Pas pour les radios natives. Un bon fieldset + legend fournit déjà aux lecteurs d'écran le contexte approprié. N'ajoutez ARIA que si vous créez un entièrement personnalisé widget (à éviter si possible).
Afficher/masquer les suivis après une sélection. Lorsqu'elle est masquée, également désactiver Ces contrôles sont désordonnés et non soumis. Une fois affichés, déplacez le focus judicieusement et assurez-vous que les libellés restent clairs.
Flux de clavier, focus visible, annonces du lecteur d'écran (légende lue, état sélectionné exprimé), zones de frappe mobiles, timing de validation (pas de harcèlement précoce) et valeurs sélectionnées persister en cas d'erreur côté serveur.