Última actualización el 01/11/2025
Los botones de opción parecen simples. Un clic, una opción. Sin embargo, también son una de las formas más fáciles de comprometer la experiencia de usuario y la accesibilidad. Ocultar la entrada nativa, omitir... fieldset más antigua y legend, o si valida de forma demasiado agresiva, enviará un formulario que confunde a los usuarios del teclado, frustra a los lectores de pantalla y pierde conversiones.
Esta guía te muestra cómo crear radios correctamente en 2025: rápidas de elegir, fáciles de escanear, totalmente accesibles y medibles. Comenzaremos con una base semántica limpia y luego aplicaremos CSS moderno para que las radios se vean como tarjetas o controles segmentados sin afectar el funcionamiento del teclado ni del lector de pantalla.
Verás patrones de validación prácticos y sencillos, diseños adaptables que mantienen los objetivos definidos en dispositivos móviles y fragmentos de producción que puedes pegar en cualquier pila. También cubriremos análisis, ideas A/B y una configuración de Formspree para que puedas obtener resultados al instante.
Para quién es esto: diseñadores, desarrolladores front-end y especialistas en marketing que se preocupan por ambos accesibilidad más antigua y conversiónNo se requieren marcos pesados, solo HTML sólido, CSS enfocado y una pequeña mejora progresiva.
Al final sabrás:
- Cuando las radios superan las selecciones, casillas de verificación o alternancias
- Cómo construir un grupo de radio accesible (
fieldset,legend, etiquetado, enfoque) - Cómo diseñar radios de "tarjeta" personalizadas sin ocultar la entrada
- Cómo validar con la API de restricciones HTML (y enviar mensajes correctamente)
- Cómo conectar radios a Formspree y medir selecciones
TL; DR
- Usa radios exactamente por uno elección dentro de un conjunto bien definido (idealmente 2–6 opciones) eso debería ser visible a la vez.
- Envuelva siempre las radios en
<fieldset>+<legend>, dales el mismoname, y hacer de cada opción una se puede hacer clic<label>. - No ocultes las entradas con
display:none; mantenerlos enfocable (por ejemplo,opacity:0; position:absolute; inset:0) entonces Tabulador/Flechas/Espacio extra. - Establezca objetivos grandes: al menos 44 × 44px, Con un anillo de enfoque Eso es claramente visible.
- Validar con el API de restricción HTML (
required,reportValidity()), luego mejoran los mensajes, no al revés. - Si tienes muchas opciones o no te resultan familiares, añade texto de ayuda debajo de cada etiqueta y considere búsqueda o agrupación .
- En el móvil, apilar en una columna; en pantallas anchas, 2–3 columnas a través de la cuadrícula CSS.
- Medida: pista impresiones, selección y envío, y prueba A/B orden de opción más antigua y copia.
- Para Formspree, mantén POST nativo como alternativa; agregar ha podido recuperar sólo como mejora progresiva.
Cuándo utilizar radios (árbol de decisiones)
P1. ¿El usuario está eligiendo exactamente una opción?
- Sí → Usa casillas de verificación (0–muchos) o un palanca (binario) o repensar la tarea.
- Sí → Vaya a la pregunta 2.
P2. ¿Es necesario que las opciones sean visibles a simple vista?
- Sí → Usa radios (ideal para escanear y elegir rápidamente).
- No (el espacio es reducido/muchas opciones) → Considere una selecciona (desplegable). Si las opciones son cruciales y se benefician de la comparación, priorice las radios y rediseñe el diseño.
Q3. ¿Cuántas opciones?
- 2-6 → Las radios son ideales.
- 7-10 → Las radios aún pueden funcionar con agrupación o un dos columnas cuadrícula; de lo contrario, utilice una lista de selección o de búsqueda.
- > 10 → Utilice un seleccionar con búsqueda paso que reduce el conjunto primero.
Q4. ¿Las opciones son estados mutuamente excluyentes?
- Sí → Radios.
- No / las combinaciones tienen sentido → Casillas de verificación.
P5. ¿La elección dará lugar a diferentes preguntas de seguimiento?
- Sí → Las radios siguen funcionando bien. Mostrar seguimiento. condicionalmente, pero mantenga los campos ocultos fuera del orden de tabulación y SR fluye hasta ser revelado.
- Sí → Grupo de radio sencillo.
Q6. ¿Familiar vs. descriptivo?
- Etiquetas familiares (por ejemplo, “Pequeño / Mediano / Grande”) → las etiquetas cortas son suficientes.
- Desconocido o de alto riesgo (por ejemplo, “SEO técnico vs. SEO de contenido”) → agregar microcopia (descripción de una línea) debajo de cada etiqueta.
Q7. ¿Móvil primero?
- Sí (siempre) → Uso pila de una columna con un espaciado generoso y texto de 16 a 18 píxeles. Promocione el los más elegidos opción hacia la parte superior.
Ejemplos prácticos
- Su formulario: "¿Qué servicio?" → Las radios son mejores que una selección porque los usuarios deben comparar las opciones. Mantenga tres tarjetas (Link Building, SEO, Desarrollo Web) con un breve subtexto.
- Velocidad de envio:3–4 velocidades mutuamente excluyentes → Radios (con deltas de precios en el subtexto).
- Frecuencia del boletín informativo:Diario/Semanal/Mensual → Radios.
- Intereses (puede elegir varios) → No radios; utilice casillas de verificación.
Anatomía de un grupo de radio adecuado
Copiar y pegar la línea base semántica (aún no hay CSS sofisticado)
<form id="quote-form" action="/es/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>
Por qué cada parte es importante
<fieldset>+<legend>Le da al grupo un nombre para los lectores de pantalla y establece el contexto para todos.- mismos
namehace que las entradas sean mutuamente excluyentes. - Clickable
<label>alrededor de cada entrada te ofrece una enorme área de toque/clic (44 px+). requireden la primera radio permite que el navegador valide el grupo de forma nativa.- Texto de ayuda (
service-hint) y texto de error (service-error) están separados para que puedas mostrar u ocultar errores sin mover las cosas. - No se necesitan roles ARIA. Las radios nativas ya exponen la semántica correcta.
CSS mínimo para mayor usabilidad (apariencia nativa, objetivos amplios, enfoque claro)
/* 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; }
Consejo:
:has()Ofrece ese "anillo de enfoque en toda la tarjeta" sin ocultar la entrada real. Si un navegador antiguo no lo admite, los usuarios aún obtienen el anillo de enfoque nativo en la radio.
Validación suave y accesible (utiliza primero el navegador)
Deje que el navegador maneje la parte "debe elegir uno" y luego muestre su error en línea si es necesario.
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');
}
});
Comportamiento que debes esperar (y probar):
- Teclado: Presione la tecla Tab una vez para ingresar al grupo; use Teclas de flecha moverse; Spacios (Amplitud) selecciona.
- Lectores de pantalla: La leyenda se lee como la etiqueta del grupo; cada opción se anuncia con el estado “seleccionado/no seleccionado”.
- toque: Al tocar la etiqueta se activa y desactiva la radio (gracias al ajuste de la etiqueta).
Radios de "tarjeta" con estilo personalizado (sin afectar la accesibilidad)
Copiar y pegar HTML
<form id="plan" action="/es/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 minimalista y robusto
: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; }
Notas de comportamiento (por qué funciona este patrón)
- No
display:noneEn la entrada → los usuarios del teclado pueden usar la tecla Tab; Teclas de flecha selección de movimiento; Spacios (Amplitud) selecciona. - Se puede hacer clic en toda la tarjeta porque la entrada está dentro de la
label. :has()Déjanos darle estilo enfocarte más antigua y comprobado Estados en la tarjeta principal sin hacks ARIA.- La cuadrícula responsiva proporciona 1–3 columnas automáticamente; los objetivos táctiles permanecen grandes.
Opcional: validación suave (nativa primero)
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;
}
});Estrategias de validación que no molestan
Una buena validación es invisible hasta que se necesita. Aquí tienes un enfoque específico para radio: rápido, accesible y cómodo para los usuarios.
Principios
- Primero lo nativo. Deje que HTML se encargue de “se debe seleccionar uno”.
- Aplazar errores. No les grites a los usuarios antes de que intenten continuar.
- Señale el problema. Anuncie los errores, centre la atención de manera inteligente y mantenga el texto breve.
- Claro el cambio. Una vez realizada una elección válida, elimine el error.
Línea base (solo nativo)
HTML solo puede hacer cumplir la regla con required en una radio del grupo.
<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>
Esto ya le ofrece: flechas del teclado, espacio para seleccionar y validación a nivel de navegador.
Errores en línea amigables (mejora progresiva)
Desactive la ventana emergente del navegador y gestione los mensajes en línea para que coincidan con su diseño.
HTML (agregar una región de error)
<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 (estilo simple)
.error { color:#b91c1c; margin-top:.5rem; font-size:.9rem; }
JS (aplazar errores, anunciarlos adecuadamente)
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();
});
});
Porque esto funciona
- No hay burbujas de información sobre herramientas en el navegador; los usuarios ven errores en línea Al lado del grupo.
- Los lectores de pantalla escuchan el leyenda, Entonces el error, gracias a
aria-describedby. - Los errores sólo aparecen después un intento de envío (o al volver a arreglarlo).
Validación suave por paso (formularios de varios pasos)
Si accede con un botón “Siguiente”, valide sólo ese paso.
document.getElementById('nextBtn').addEventListener('click', () => {
if (!hasSelection()) {
showError();
} else {
clearError();
// advance to next step...
}
});
Mensajes personalizados con la API de restricción (opcional)
Si prefiere mantener el modelo de validez del navegador, configure un mensaje personalizado en el first radio en el grupo.
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('');
}
});
Use esto cuando desee la “burbuja” nativa más su propio texto; de lo contrario, apéguese al método en línea anterior para una interfaz de usuario consistente.
Resumen de errores (para formularios largos)
Si su formulario es largo, agregue un resumen superior que se vincula con el área del problema.
<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
}
Respaldos del lado del servidor y de la red
La validación del lado del cliente mejora la experiencia del usuario, pero Validar también en el servidor (o por su servicio de envío). Si el envío falla, devolver la página con:
- El anterior la selección persistió y
- La mismo error en línea Al lado del grupo.
Integración de Formspree (mejora progresiva)
Haga que las radios funcionen incluso si falla JavaScript y luego mejórelas para obtener un agradecimiento más fluido y mejores análisis.
HTML nativo (siempre funciona)
<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>
Lo que esto te ofrece:
- Funciona con JS desactivado (bueno para SEO, confiabilidad).
- El navegador maneja “uno debe ser seleccionado” (
requireden la primera radio). - Formspree recibe
service,name,emaily_subject.
Mejora de JS (buena experiencia de usuario, mejor seguimiento)
Interceptar el envío, validar, enviar con fetchy mostrar un agradecimiento en línea (o redirección).
<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>
Notas
- Establecer
Accept: application/jsonDe esta forma, Formspree devuelve JSON y usted puede decidir el éxito o el fracaso de forma clara. - Usa
form.reportValidity()De esta forma, el navegador aplica la selección de radio y el formato del correo electrónico antes de enviarlo. - Mantener lo nativo
action/methodAsí que el formulario todavía funciona sin JS.
Control de spam sin afectar las conversiones
- Tarro de miel (incluido arriba) atrapa muchos bots sin ninguna fricción.
- Sincronización:añadir un oculto
started_atcuando se carga la página; ignore los envíos que tardan más de, digamos, 2 segundos. - Señales de contenido:Si ve valores basura repetidos, fíltrelos en las reglas de Formspree o en el posprocesamiento.
<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
Líneas de asunto que reflejan la elección de radio (bandeja de entrada limpia)
Puedes adaptar _subject de la radio seleccionada:
const service = fd.get('service'); // e.g., "seo"
fd.set('_subject', `New ${service} lead from Contact form`);
Escollos comunes a evitar
- Ocultar radios con
display:none. Usaopacity:0; position:absolute; inset:0Dentro de la etiqueta para que los teclados sigan funcionando. - Olvidando
fieldset/legend. Los lectores de pantalla confían en ellos para obtener contexto. - Sobrevalidación temprana. Mostrar errores después de enviar/siguiente y luego borrar tan pronto como el usuario arregle el grupo.
- No capturar el contexto Agregar la extensión de
page_url,referrery los campos UTM, te lo agradecerás más tarde.
Internacionalización e inclusión
Lenguaje, etiquetas y tono
- Utilice la página
lang(y cambiar páginas según la configuración regional):<html lang="en"> … </html> - Mantenga las etiquetas breves y descriptivo, evite la jerga específica de la cultura.
- Utilice neutral fraseología (“Elige un plan”) en lugar de suposiciones (“¿Cuál es tu presupuesto?”).
- "Localizar" errores más antigua y texto de ayuda con un diccionario sencillo codificado por
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');
Etiquetas largas y opciones multilínea
- Dejar etiquetas envolver; Nunca trunques palabras cruciales.
- Mantenga el área de impacto grande incluso cuando el texto se ajusta: use relleno en el Etiqueta, no sólo la entrada.
.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; }
Soporte de derecha a izquierda (RTL)
Usa CSS propiedades lógicas más antigua y :dir() Así que no necesitas estilos separados.
/* 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; }
Accesibilidad más allá de ARIA
- Asegúrese de que contraste ≥ 4.5:1 para etiquetas y estados de enfoque.
- Tocar objetivos ≥ 44 × 44 px (relleno en lugar de tamaño de fuente).
- No comunique el estado solo por el color; use bordes, iconos o texto.
Lista de verificación de pruebas (versión para imprimir)
- Semántica:Las radios están dentro
fieldsetcon un visiblelegend. - Teclado:La pestaña entra al grupo; flechas mover; Spacios (Amplitud) selecciona.
- lectores de pantalla:Leyenda anunciada; cada opción dice seleccionada/no seleccionada.
- Enfócate:Anillo visible en el tarjeta completa (y en la entrada nativa).
- Área de impacto:Se puede hacer clic en toda la etiqueta; funciona en dispositivos móviles.
- de calidad:No hay errores hasta enviar/siguiente; los errores se borran al cambiar.
- Copiar:Las opciones son breves; el texto de ayuda aclara las diferencias.
- Diseño:1 columna sobre teléfonos; 2–3 sobre computadoras de escritorio; sin recorte por desbordamiento.
- i18n:Errores/etiquetas localizados; el texto largo se ajusta correctamente; RTL correcto.
- Análisis estadísticos:Eventos para impresión, selección, envío.
- Rendimiento:Sin bibliotecas de interfaz de usuario pesadas; CSS mínimo; sin cambios de diseño.
Rendimiento y mantenibilidad
- Favor entradas nativas + CSS ligero; evitar reemplazar radios con divs.
- Mantenga CSS modular (propiedades personalizadas para colores, radio, anillo).
- Evitando
display:noneen las entradas; usoopacity:0; position:absolute; inset:0Por lo que la accesibilidad permanece intacta. - Aplazar el JS no crítico; mantener las mejoras por debajo de ~2–5 KB cuando sea posible.
- Usa consultas de contenedor (cuando esté disponible) en lugar de puntos de interrupción de medios complejos para formularios integrados.
@container (min-width: 680px) {
.card-group { grid-template-columns: 1fr 1fr 1fr; }
}
Análisis y experimentación
Intención del seguimiento y puntos de fricción:
// 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 });
Prueba A/B:
- Orden de opción (los más elegidos primero)
- copia de llamada a la acción (“Obtener mi propuesta gratuita” vs. “Obtener mi cotización”)
- Texto de ayuda presencia
Antipatrones (no hagas esto)
- Ocultar entradas con
display:none(rompe el teclado y los SR). - Desaparecido
fieldset/legend. - Pequeñas áreas afectadas (solo se puede hacer clic en el pequeño punto).
- Estado de selección de solo color; enfoque invisible.
- Validación antes El usuario intenta continuar.
- Incluye entre 10 y 20 opciones en las radios; en su lugar, utilice una selección con función de búsqueda.
Lista de verificación final
fieldset+legendpresente- mismos
nameúnicovalues - Las etiquetas envuelven las entradas; se puede hacer clic en toda la tarjeta
- Anillo de enfoque claramente visible
requireden la primera radio; errores suaves en línea- Cuadrícula de 1 a 3 columnas con gran capacidad de respuesta y objetivos táctiles
- Cadenas localizadas; RTL seguro mediante propiedades lógicas
- Análisis al ver/seleccionar/enviar
- La validación del lado del servidor refleja las reglas del cliente
Recursos
Especificaciones y patrones de creación
- Estándar HTML –
<input type="radio">:semántica nativa, agrupación porname, comportamiento del formulario. - Prácticas de autoría de WAI-ARIA 1.2 – Radio Group: comportamiento esperado del teclado (Tab en el grupo, flecha moverse, Spacios (Amplitud) para seleccionar), patrones de etiquetado.
- WCAG 2.2 Elementos esenciales para radios:
- 2.1.1 Teclado (todo operable mediante teclado)
- 2.4.7 Enfoque visible (foco claramente visible)
- 2.5.5 Tamaño objetivo (≥44×44px recomendado)
- 1.4.3 Contraste (Mínimo)
Guías de referencia
- DND:Entrada de radio,
label,fieldset/legend, validación de formulario (API de validación de restricciones). - Sistema de diseño GOV.UK – Radios:Excelente guía, probada en batalla, sobre redacción y errores.
- Componentes inclusivos (Heydon Pickering):patrones prácticos y críticas.
Consulta nuestra última entrada de blog sobre Cómo buscar oportunidades de publicaciones de invitados en Ahrefs (Guía paso a paso)
Pruebas y herramientas
- Hacha DevTools (extensión del navegador): comprobaciones automáticas de a11y.
- Insights de accesibilidad – pruebas guiadas rápidas.
- Lighthouse – señales de rendimiento + accesibilidad.
- Comprobador de contraste WebAIM – verificar el contraste de color.
- lectores de pantalla Para comprobar el comportamiento real:
- NVDA (Windows), MANDÍBULAS (Windows), VoiceOver (MacOS/iOS), TalkBack (Androide).
- Pase de teclado manual: Tabular en el grupo → Las flechas se mueven → La barra espaciadora selecciona → el anillo de enfoque permanece visible.
Consulta nuestro Revisión de Notions Marketing
Preguntas frecuentes (breves y prácticas)
Aspirar a 2-6Si tiene entre 7 y 10, agrúpelos o use una cuadrícula de dos columnas. Si tiene más de ~10, cambie a una seleccionar (con búsqueda) o rediseñar para limitar las opciones primero.
Sólo si hay una incumplimiento por mayoría clara y no sesgará los datos. La preselección acelera la finalización, pero puede reducir la elección deliberada. Si no está seguro, no preseleccionar.
Radios:opción única, las opciones deben ser Visible lado a lado.
Seleccione :una única opción cuando el espacio es reducido o las opciones son muchas.
Las casillas de verificación: 0–muchos opciones (no mutuamente excluyentes).
Sí. Mantén el entrada nativa enfocable (no utilizar display:none). Poner la entrada dentro del módulo <label>, configúralo en opacity:0; position:absolute; inset:0;y estilizar la etiqueta. Usar :has(input:focus-visible) más antigua y :has(input:checked) para impulsar el enfoque/estados seleccionados.
Utilice HTML primero: coloque required en la primera radio y llama form.reportValidity() (o dejar que el navegador se encargue del envío). Para una interfaz más atractiva, muestra un breve error en línea cerca del grupo después el primer intento, y claro sobre el cambio.
role="radiogroup"?No apto para radios nativas. Un adecuado fieldset + legend Ya proporciona a los lectores de pantalla el contexto adecuado. Agregue ARIA solo si está creando un totalmente personalizado widget (evítelo si es posible).
Mostrar/ocultar los seguimientos después una selección. Cuando está oculto, también inhabilitar Esos controles están fuera de orden de tabulación y no se envían. Al revelarlos, mueva el foco con cuidado y asegúrese de que las etiquetas permanezcan claras.
Flujo del teclado, foco visible, anuncios del lector de pantalla (leyenda leída, estado seleccionado expresado), áreas de acceso móvil, tiempo de validación (sin insistencia temprana) y valores seleccionados persistir Error del lado del servidor.