Muchos elementos de HTML ya son accesibles de serie: solo hay que usarlos. Cuando el diseño pide algo que no existe en
HTML, aquí tienes cómo construirlo para que cumpla.
La regla de oro
Si existe un elemento HTML que hace lo que necesitas, úsalo. Un button trae de serie el foco, el teclado, el rol y los estados. Con un div tienes que programarlo todo, y es fácil que falte algo. ARIA cambia lo que se anuncia, pero no añade comportamiento.
Botón
<button>Nativo de HTML · Acciones
Para cualquier acción dentro de la página: enviar, abrir, guardar, cerrar.
Lo que te da el navegador
Se anuncia como «botón» con su texto como nombre.
Se enfoca con Tab y se activa con Enter y con Espacio.
Con disabled sale del orden de foco y se anuncia como no disponible.
Dentro de un formulario, type="submit" lo envía sin JavaScript.
Uso con teclado
Tecla
Qué hace
Tab
Llega al botón
Enter o Espacio
Lo activa
Vista previaPruébala con el ratón y con el teclado
El clásico «encendido / apagado» de los ajustes. Una casilla (input type="checkbox") con estilos ya es accesible; si el diseño lo pide como botón, usa role="switch".
Lo que tienes que añadir
role="switch": se anuncia como interruptor.
aria-checked="true" o "false": el estado actual.
El texto del botón es su nombre; el dibujo del interruptor va con aria-hidden.
Dibuja el estado a partir de aria-checked: así lo que se ve y lo que se anuncia nunca se separan.
Uso con teclado
Tecla
Qué hace
Tab
Llega al interruptor
Espacio o Enter
Cambia el estado
Vista previaPruébala con el ratón y con el teclado
// Interruptor: un botón con role="switch". El botón ya responde a Enter y Espacio;// solo hay que cambiar aria-checked, y el estilo visual se basa en ese atributo.for (const sw of document.querySelectorAll('[role="switch"]')) { sw.addEventListener("click", () => { const on = sw.getAttribute("aria-checked") === "true"; sw.setAttribute("aria-checked", String(!on)); });}
CSS
/* El estado visual sale del atributo ARIA */.switch[aria-checked="true"] .switch-track { background: var(--accent); }.switch[aria-checked="true"] .switch-track::after { transform: translateX(1.25rem); }
Para agrupar opciones relacionadas, como botones de opción o casillas.
Lo que te da el navegador
Al entrar en el grupo, el lector anuncia la pregunta (la legend).
Tab entra y sale del grupo de una vez; las flechas cambian de opción.
Cada opción anuncia si está marcada y su posición («2 de 3»).
Uso con teclado
Tecla
Qué hace
Tab
Entra en el grupo, en la opción marcada
Flechas
Cambian de opción
Espacio
Marca la opción enfocada
Vista previaPruébala con el ratón y con el teclado
HTML
<fieldset> <legend>Tipo de entrega</legend> <label><input type="radio" name="demo-entrega" value="domicilio" checked> A domicilio</label> <label><input type="radio" name="demo-entrega" value="tienda"> Recoger en tienda</label> <label><input type="radio" name="demo-entrega" value="punto"> Punto de recogida</label></fieldset>
Ojo: Antes de construir un desplegable a medida, prueba a dar estilo al select. Con appearance: base-select (ya disponible en Chrome y Edge) se puede personalizar casi por completo.
/* La opción resaltada tiene que verse, no solo anunciarse */[role="option"][aria-selected="true"] { background: var(--accent); color: var(--accent-ink);}
Vista previaPruébala con el ratón y con el teclado
<a href="/criterios/">Ver los criterios WCAG</a><!-- Si descarga un archivo, dilo en el texto --><a href="/guia.pdf">Guía de accesibilidad (PDF, 1,2 MB)</a>
Ojo: ¿Lleva a otro sitio? Enlace. ¿Hace algo aquí? Botón. Mezclarlos confunde a quien usa lector de pantalla, porque espera un comportamiento distinto.
// Pestañas con activación automática: las flechas cambian de pestaña y muestran su panel.// Solo la pestaña activa está en el orden de Tab (tabindex 0); el resto tiene -1.for (const list of document.querySelectorAll('[role="tablist"]')) { const tabs = [...list.querySelectorAll('[role="tab"]')]; function select(tab) { for (const t of tabs) { const active = t === tab; t.setAttribute("aria-selected", String(active)); t.tabIndex = active ? 0 : -1; document.getElementById(t.getAttribute("aria-controls")).hidden = !active; } tab.focus(); } list.addEventListener("click", (e) => { const tab = e.target.closest('[role="tab"]'); if (tab) select(tab); }); list.addEventListener("keydown", (e) => { const i = tabs.indexOf(document.activeElement); const next = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key]; if (next === undefined) return; e.preventDefault(); select(tabs[(next + tabs.length) % tabs.length]); });}
Un botón que muestra y oculta una lista de enlaces. Es un patrón «disclosure»: no uses role="menu", que es para menús de aplicación (como Archivo o Editar) y obliga a navegar con flechas.
Lo que tienes que añadir
Un button con aria-expanded indica si la lista está abierta.
aria-controls apunta a la lista que muestra.
Esc cierra y devuelve el foco al botón.
Los enlaces siguen siendo enlaces normales: se recorren con Tab.
Uso con teclado
Tecla
Qué hace
Enter o Espacio
Abre o cierra la lista
Tab
Recorre los enlaces
Esc
Cierra y vuelve al botón
Vista previaPruébala con el ratón y con el teclado
const boton = document.querySelector('[aria-controls="menu-servicios"]');const menu = document.getElementById("menu-servicios");function mostrar(abrir) { boton.setAttribute("aria-expanded", String(abrir)); menu.hidden = !abrir;}boton.addEventListener("click", () => mostrar(menu.hidden));// Esc cierra y devuelve el foco al botónboton.parentElement.addEventListener("keydown", (e) => { if (e.key === "Escape" && !menu.hidden) { mostrar(false); boton.focus(); }});
Ojo: Alternativa casi nativa: con popovertarget en el botón y popover en la lista, el navegador gestiona Esc, el clic fuera y el estado expandido sin JavaScript.
Para mostrar y ocultar contenido, como preguntas frecuentes. Sin una línea de JavaScript.
Lo que te da el navegador
El summary se anuncia como un control con estado «contraído» o «expandido».
Se abre y se cierra con Enter o Espacio.
Con el mismo atributo name en varios details, solo uno queda abierto a la vez.
Uso con teclado
Tecla
Qué hace
Tab
Llega al summary
Enter o Espacio
Abre o cierra
Vista previaPruébala con el ratón y con el teclado
¿Cuánto tarda el envío?
Entre 24 y 48 horas en la península.
¿Puedo devolver un pedido?
Sí, durante 30 días desde la entrega.
HTML
<details name="demo-faq"> <summary>¿Cuánto tarda el envío?</summary> <p>Entre 24 y 48 horas en la península.</p></details><details name="demo-faq"> <summary>¿Puedo devolver un pedido?</summary> <p>Sí, durante 30 días desde la entrega.</p></details>
El alt es lo que se lee en lugar de la imagen. figcaption añade un pie visible para todo el mundo.
Lo que te da el navegador
Se anuncia como imagen y lee el alt.
Con alt="" el lector la ignora (decorativa).
Sin alt, muchos lectores leen el nombre del archivo.
Vista previaAsí se ve
Evolución de las ventas online, 2024 a 2026 (datos de ejemplo).
HTML
<figure> <img src="ventas.png" alt="Las ventas online crecieron un 30 % entre 2024 y 2026."> <figcaption>Evolución de las ventas online, 2024 a 2026.</figcaption></figure><img src="separador.svg" alt="">
Diapositivas que rotan. Antes de usarlo, pregúntate si hace falta: mucha gente no pasa de la primera. Si lo usas, que se pueda pausar y manejar con botones.
Lo que tienes que añadir
Botón de pausa, el primero en el orden de foco.
Botones anterior y siguiente, además de cualquier gesto de deslizar.
Cada diapositiva se identifica como «1 de 3».
Se detiene cuando el foco entra y no arranca si la persona prefiere menos movimiento.
Los cambios se anuncian (aria-live="polite") solo cuando los provoca la persona.
Uso con teclado
Tecla
Qué hace
Tab
Pausa, anterior, siguiente y contenido de la diapositiva
Enter o Espacio
Activa el botón enfocado
Vista previaPruébala con el ratón y con el teclado