CSS accesible
En esta lección
- El foco tiene que verse
- Contraste de color
- Tamaño de texto en rem
- Objetivos táctiles grandes
- Respetar a quien no quiere movimiento: prefers-reduced-motion
- Más contraste: prefers-contrast y forced-colors
- Ocultar a la vista, pero no al lector de pantalla
- No transmitas información solo con color
- Orden visual y orden del DOM
- Zoom al 200 %
- Errores frecuentes
- Resumen
Una web accesible es la que puede usar cualquier persona: quien navega solo con el teclado, quien usa un lector de pantalla, quien ve mal los colores, quien amplía el texto al 200 % o quien se marea con las animaciones. En España, además, la accesibilidad es obligatoria para las webs públicas y, desde 2025, para muchos servicios privados. En la lección de accesibilidad en HTML viste la base: etiquetas semánticas, textos alternativos y etiquetas en los formularios. Aquí verás la parte que depende del CSS, que es mucha: con unas pocas líneas puedes hacer una web imposible de usar con el teclado, o al revés, una que funcione bien para todo el mundo.
El foco tiene que verse
Mucha gente navega sin ratón: con la tecla Tab salta de un enlace o botón al siguiente, y con Enter o la barra espaciadora lo activa. Para saber dónde está, el navegador dibuja un anillo de foco (focus ring) alrededor del elemento activo.
El error más grave del CSS accesible es este:
/* NO HAGAS ESTO */
*:focus {
outline: none;
}
Esa regla, que se copia de plantillas antiguas porque “el borde azul queda feo”, deja a los usuarios de teclado a ciegas: no saben dónde están. Es como quitarle el puntero del ratón a alguien.
:focus-visible: foco solo cuando hace falta
La queja original tenía su parte de razón: al hacer clic con el ratón en un botón, a veces aparecía el anillo y molestaba. La pseudoclase :focus-visible lo resuelve: el navegador decide si el foco debe verse (con teclado, sí; con un clic de ratón en un botón, normalmente no).
:focus-visible {
outline: 3px solid #e76f51;
outline-offset: 3px;
}
Con esta regla tienes un foco claro, de un color de tu marca, que aparece cuando navegas con el teclado. En los campos de texto el navegador lo muestra siempre, también con el ratón, porque ahí indica dónde vas a escribir.
Si de verdad quieres cambiar el estilo del foco, sustitúyelo, no lo elimines:
.boton:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
box-shadow: 0 0 0 5px #ffd166;
}
¿Por qué outline y no border? Porque outline no ocupa espacio en el modelo de caja: el elemento no cambia de tamaño ni empuja a sus vecinos al recibir el foco. Además, en el modo de alto contraste de Windows, outline se respeta y box-shadow desaparece (lo verás más abajo).
Tienes más sobre :focus, :focus-visible y :focus-within en pseudoclases a fondo.
Consejo: prueba tu web solo con el teclado al menos una vez:
Tabpara avanzar,Shift + Tabpara retroceder. Si en algún momento no sabes dónde está el foco, tienes un problema.
Contraste de color
El texto debe destacar lo suficiente sobre su fondo. Las pautas internacionales de accesibilidad (WCAG) miden el contraste como una relación entre 1:1 (mismo color) y 21:1 (negro sobre blanco), y piden:
| Elemento | Contraste mínimo (nivel AA) |
|---|---|
| Texto normal | 4,5:1 |
| Texto grande (24 px, o 18,66 px en negrita) | 3:1 |
| Iconos, bordes de campos, anillo de foco | 3:1 |
El gris claro sobre blanco que tanto se ve en diseños “minimalistas” (#aaa sobre #fff) tiene un contraste de 2,3:1: no pasa. #595959 sobre blanco ya llega a 7:1.
Para comprobarlo, en DevTools selecciona el texto, pulsa el cuadrado de color en el panel Styles y mira la línea Contrast ratio. Si no cumple, el propio selector te propone un tono que sí.
Fíjate también en el texto sobre imágenes: un titular blanco sobre una foto puede leerse bien en una parte y mal en otra. Pon debajo una capa oscura semitransparente o un degradado.
Tamaño de texto en rem
Muchas personas aumentan el tamaño de letra por defecto en la configuración del navegador (de 16 px a 20 o 24 px). Si escribes los tamaños en píxeles, ignoras esa preferencia:
/* Mal: el texto mide siempre 14 px */
body { font-size: 14px; }
/* Bien: 1rem es el tamaño que el usuario ha elegido */
body { font-size: 1rem; }
h1 { font-size: 2rem; }
.nota { font-size: 0.875rem; }
rem es relativo al tamaño de letra del elemento raíz, que por defecto sigue la configuración del usuario. Tienes las unidades explicadas en unidades.
Otras reglas para un texto legible:
- Interlineado de al menos 1,5 en los párrafos (
line-height: 1.5). - Líneas no muy largas: unos 60-75 caracteres (
max-width: 65ch). - No justificar el texto (
text-align: justify): crea huecos irregulares entre palabras que dificultan la lectura, sobre todo a personas con dislexia. - No escribir en mayúsculas frases largas; si quieres mayúsculas, usa
text-transform: uppercasey deja el texto normal en el HTML (algunos lectores de pantalla deletrean las palabras escritas en mayúsculas).
Objetivos táctiles grandes
Un enlace diminuto es difícil de pulsar con el dedo, y más para personas con temblor o con poca destreza. Las pautas piden que los objetivos táctiles (touch targets) midan al menos 24 × 24 px (nivel AA), y lo recomendable son 44 × 44 px.
.menu a {
display: inline-block;
padding: 12px 16px;
min-height: 44px;
}
.boton-icono {
width: 48px;
height: 48px;
}
Fíjate en que no hace falta que el icono sea grande; basta con que lo sea la zona pulsable, gracias al padding. Deja también espacio entre botones contiguos para no pulsar el que no querías.
Respetar a quien no quiere movimiento: prefers-reduced-motion
Las animaciones grandes (paralaje, elementos que vuelan, zoom al hacer scroll) pueden provocar mareos y náuseas a personas con trastornos vestibulares. Por eso los sistemas operativos tienen una opción de “reducir movimiento”, y CSS la detecta:
.tarjeta {
transition: transform 0.3s;
}
.tarjeta:hover {
transform: translateY(-6px);
}
@media (prefers-reduced-motion: reduce) {
.tarjeta {
transition: none;
}
.tarjeta:hover {
transform: none;
}
}
“Reducir” no significa “quitar todo”: un cambio de color o un fundido suave suelen estar bien. Lo que hay que eliminar o acortar son los desplazamientos, giros y zooms grandes. Una animación que transmite información (un indicador de “cargando”) puede seguir, más lenta.
La otra forma de plantearlo es al revés: escribir las animaciones solo para quien no ha pedido nada.
@media (prefers-reduced-motion: no-preference) {
html { scroll-behavior: smooth; }
.portada h1 { animation: aparecer 0.8s ease-out; }
}
Lo verás aplicado en transiciones, animaciones y scroll.
Más contraste: prefers-contrast y forced-colors
prefers-contrast
Algunas personas piden más contraste en la configuración de su sistema. Puedes reforzar bordes y textos suaves para ellas:
@media (prefers-contrast: more) {
:root {
--texto-suave: #1f2328; /* el gris suave pasa a ser casi negro */
--borde: #000;
}
.boton { border: 2px solid currentColor; }
}
Si organizas los colores con variables, basta con redefinir unas cuantas.
forced-colors: modo de alto contraste
Windows tiene un modo de contraste alto en el que el sistema sustituye todos tus colores por una paleta muy reducida (por ejemplo, texto amarillo sobre negro). El navegador elimina fondos, sombras (box-shadow) y degradados. Puedes detectarlo con forced-colors: active.
Lo importante es que tu web siga siendo comprensible sin tus colores:
- Si un botón solo se distingue por su color de fondo, en este modo desaparece. Dale un borde, aunque sea transparente:
border: 2px solid transparent. En modo forzado el borde se pinta con el color del sistema y el botón vuelve a verse. - Si tu foco es solo un
box-shadow, desaparece. Usaoutline, aunque seaoutline: 2px solid transparent.
.boton {
background: #1f5fc9;
color: white;
border: 2px solid transparent; /* invisible normalmente, visible en alto contraste */
}
@media (forced-colors: active) {
/* Un icono SVG pintado con un color fijo: que use el color del texto del sistema */
.icono { fill: CanvasText; }
}
CanvasText es un color del sistema: el color que el usuario ha elegido para el texto. También existen Canvas (fondo), LinkText (enlaces) o ButtonText (texto de botones). Dentro de forced-colors, usa estos nombres en lugar de colores concretos.
Para probarlo sin Windows, en DevTools abre el panel Rendering y en Emulate CSS media feature forced-colors elige active.
Ocultar a la vista, pero no al lector de pantalla
Un botón que solo tiene un icono (un corazón, una lupa) no dice nada a quien usa un lector de pantalla. Necesita un texto que no se vea pero que sí se lea. Ni display: none ni visibility: hidden sirven: ocultan el texto también al lector.
La solución es una clase de utilidad que todos los proyectos tienen, con nombres como .visually-hidden o .sr-only (screen reader only):
.visually-hidden {
position: absolute !important;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
<button class="boton-icono" type="button">
<svg aria-hidden="true">...</svg>
<span class="visually-hidden">Buscar</span>
</button>
El texto existe, mide un píxel y está recortado: no ocupa sitio ni se ve, pero el lector de pantalla anuncia “Buscar, botón”. Verás versiones antiguas con clip: rect(0 0 0 0); clip está obsoleto y hoy se usa clip-path.
Un uso relacionado es el enlace para saltar al contenido: el primer elemento de la página, oculto hasta que recibe el foco con el teclado, que permite saltarse el menú:
.saltar {
position: absolute;
left: 8px;
top: -100px;
}
.saltar:focus {
top: 8px;
}
No transmitas información solo con color
Alrededor del 8 % de los hombres tiene algún tipo de daltonismo: muchos confunden el rojo y el verde. Si un formulario marca los campos con error solo con un borde rojo, esa persona no sabe cuál falla.
Acompaña siempre el color de otra pista: un texto, un icono, un grosor, un subrayado.
/* Mal: el error solo se nota por el color */
.campo.error { border-color: #b42318; }
/* Bien: color + grosor + mensaje de texto */
.campo.error {
border: 2px solid #b42318;
background-image: url("icono-error.svg");
background-repeat: no-repeat;
background-position: right 8px center;
}
<p class="mensaje-error">El correo debe contener una @.</p>
Lo mismo con los enlaces dentro de un párrafo: si solo se distinguen por el color, quítales el subrayado únicamente cuando el contraste con el texto de alrededor sea suficiente. Lo más seguro es mantenerlo (text-decoration: underline). Y en los gráficos, además de colores distintos, usa etiquetas o patrones.
Orden visual y orden del DOM
Con Flexbox y Grid puedes cambiar el orden visual de los elementos (order, flex-direction: row-reverse, colocar ítems de Grid en cualquier celda). Pero el teclado y el lector de pantalla siguen el orden del HTML, no el visual.
/* El botón "Comprar" aparece primero en pantalla, pero en el HTML va último */
.acciones { display: flex; }
.acciones .comprar { order: -1; }
Quien navega con Tab verá que el foco salta de forma incoherente: primero los botones de la derecha y luego el de la izquierda. La regla es sencilla: el orden del HTML debe ser el orden lógico. Usa order y similares solo para ajustes visuales que no cambien el sentido de lectura, y si quieres otro orden, cámbialo en el HTML.
Zoom al 200 %
Las pautas piden que la web funcione al ampliarla al 200 % sin perder contenido ni funciones, y que a 320 px de ancho no haya scroll horizontal. Un diseño responsive bien hecho ya cumple casi todo, porque al ampliar el zoom el navegador se comporta como si la pantalla fuera más estrecha.
Para comprobarlo, pulsa Ctrl y + varias veces hasta llegar al 200 % y revisa:
- Que no aparezca scroll horizontal (ojo con los anchos fijos en píxeles).
- Que ningún texto quede cortado dentro de una caja con
heightfija yoverflow: hidden. Usamin-heighten lugar deheight. - Que las cabeceras y barras fijas no ocupen media pantalla. Al 200 % una cabecera
stickyde 100 px puede tapar casi todo; plantéate que deje de ser fija en pantallas bajas. - Que los menús sigan pudiendo abrirse y usarse.
Errores frecuentes
outline: nonesin alternativa. Deja a los usuarios de teclado sin saber dónde están. Usa:focus-visiblecon un estilo propio.- Texto gris claro sobre blanco. Comprueba siempre el contraste en DevTools.
- Tamaños de letra en
px. Ignoran la configuración del usuario. Usarem. - Botones e iconos diminutos. Como mínimo 24 × 24 px de zona pulsable; mejor 44 × 44.
- Ocultar con
display: noneel texto de un botón de icono. El lector de pantalla tampoco lo lee. Usa.visually-hidden. - Errores marcados solo en rojo. Añade un texto o un icono.
- Cambiar el orden con
ordery romper la navegación con teclado. - Animaciones grandes sin
prefers-reduced-motion. - Alturas fijas que cortan el texto al hacer zoom.
Resumen
| Técnica | Para qué | Ejemplo |
|---|---|---|
:focus-visible | Foco visible al usar el teclado | outline: 3px solid; outline-offset: 3px; |
| Contraste 4,5:1 | Texto legible | Comprobar en DevTools |
rem | Respetar el tamaño de letra elegido | font-size: 1rem; |
| Objetivos de 44 px | Pulsar fácil con el dedo | min-height: 44px; padding: 12px; |
prefers-reduced-motion | Menos movimiento a quien lo pide | @media (prefers-reduced-motion: reduce) |
prefers-contrast | Más contraste a quien lo pide | @media (prefers-contrast: more) |
forced-colors | Modo de alto contraste de Windows | border: 2px solid transparent; |
.visually-hidden | Texto solo para lectores de pantalla | clip-path: inset(50%); |
| Color + otra pista | Información para daltónicos | Icono o texto junto al rojo |
| Orden del HTML | Navegación lógica con teclado | Evitar order que cambie el sentido |
| Zoom 200 % | Ampliar sin perder contenido | min-height en vez de height |
Con estas bases tu CSS ya es accesible. Ahora toca conocer las novedades del lenguaje que más se usan hoy: CSS moderno.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.