Addaw, ir a inicio Donar

Lección 3 de 10 · 15 min de lectura · Revisado el 26/10/2026

CSS para la interacción: foco visible, objetivos de puntero, elementos fijos y contenido emergente

En la lección anterior viste cómo el CSS decide qué se oculta, qué se inyecta y en qué orden se muestra. Esta lección se ocupa de otra parte del CSS que pesa igual: la que gobierna la interacción. El indicador de foco, el tamaño de los botones, las cabeceras que se quedan fijas al hacer scroll y las burbujas que aparecen al pasar el puntero se escriben casi siempre en la hoja de estilos, y de ellas dependen cinco criterios: 2.4.7 Foco visible, 2.4.11 Foco no oculto (mínimo), 2.4.13 Apariencia del foco (AAA), 2.5.8 Tamaño del objetivo (mínimo) y 1.4.13 Contenido con hover o foco.

Un indicador de foco que funcione sobre cualquier fondo

La seudoclase :focus se aplica siempre que un elemento recibe el foco; :focus-visible solo cuando el navegador considera que la persona necesita verlo: al llegar con el teclado, al enfocar por programa tras una acción de teclado y siempre en los campos de texto. Al hacer clic con el ratón en un botón, en cambio, no se activa. Así desaparece la excusa de quitar el contorno «porque molesta al hacer clic».

Error frecuente

/* Quita el foco a todo el mundo, también a quien navega con teclado */
*:focus { outline: none; }

/* Indicador hecho solo con sombra: desaparece en modo de colores forzados */
.boton:focus { outline: none; box-shadow: 0 0 0 3px #7aa7ff; }

El primero incumple 2.4.7 en toda la web de un plumazo. El segundo parece correcto, pero los modos de colores forzados eliminan box-shadow, y el foco desaparece justo para las personas que más lo necesitan (lo verás a fondo en la lección siguiente). Además, un azul claro sobre fondo blanco no alcanza el 3:1 de 1.4.11 Contraste no textual.

Una base robusta usa outline, que no altera el tamaño de la caja, sigue el border-radius en los navegadores actuales y sobrevive a los colores forzados. Para que el indicador se vea sobre fondos claros, oscuros y fotografías, combina un contorno oscuro con un halo claro:

:focus-visible {
  outline: 3px solid #1a1a1a;
  outline-offset: 0;
  box-shadow: 0 0 0 6px #ffffff; /* halo claro por fuera del contorno */
}

Queda un anillo oscuro de 3 píxeles rodeado de otro claro. Si los colores forzados eliminan la sombra, el contorno sigue ahí con el color del sistema.

Cuánto foco es suficiente

  • 2.4.7 (AA) solo pide que el foco sea visible. 1.4.11 añade que el indicador, como información visual necesaria para identificar un estado, tenga un contraste de al menos 3:1 con los colores adyacentes.
  • 2.4.13 Apariencia del foco (AAA) concreta una medida: un área al menos equivalente a un perímetro de 2 píxeles CSS alrededor del componente y un contraste de 3:1 entre el estado enfocado y el no enfocado. Un contorno sólido de 2 o 3 píxeles con buen contraste lo cumple con holgura; un subrayado fino o un ligero cambio de color de fondo, no.

Que el foco no quede tapado: cabeceras fijas, barras y avisos

2.4.11 Foco no oculto (mínimo) (AA, nuevo en WCAG 2.2) exige que, cuando un componente recibe el foco, no quede completamente oculto por contenido creado por el autor. Su versión AAA, 2.4.12 Foco no oculto (mejorado), pide que no quede oculta ninguna parte. Los culpables habituales son elementos con position: sticky o position: fixed: la cabecera sticky (pegajosa: se queda fija arriba al desplazar la página), la barra de acciones inferior en móvil, el banner de cookies, el widget de chat y el botón «volver arriba».

Cuando la persona pulsa Mayús + Tab para volver hacia arriba, el navegador desplaza la página lo justo para que el elemento enfocado entre en la ventana… y lo deja exactamente debajo de la cabecera fija. La solución está en CSS: scroll-padding en el contenedor de desplazamiento (normalmente html) reserva un margen que el navegador respeta al desplazar hacia un elemento enfocado o hacia un ancla.

Ejemplo

Sede electrónica con una cabecera fija y una barra inferior con el botón «Continuar trámite»:

:root {
  --alto-cabecera: 4.5rem;
  --alto-barra: 4rem;
}
html {
  scroll-padding-top: calc(var(--alto-cabecera) + 1rem);
  scroll-padding-bottom: calc(var(--alto-barra) + 1rem);
}
.cabecera { position: sticky; top: 0; min-height: var(--alto-cabecera); }
.barra-tramite { position: fixed; bottom: 0; min-height: var(--alto-barra); }

/* Con zoom alto o pantallas bajas, la cabecera deja de ser fija */
@media (max-height: 30em) {
  .cabecera { position: static; }
  html { scroll-padding-top: 1rem; }
}

La última regla es clave: al 400 % de zoom en un portátil, la ventana mide unos 200 píxeles CSS de alto; una cabecera fija de 72 píxeles se comería un tercio de la pantalla. Soltarla mejora a la vez 2.4.11 y 1.4.10 Reajuste de los elementos.

Si la altura de la cabecera cambia, usa unidades rem o actualiza la variable con un ResizeObserver. Para los banners y avisos no modales, la regla es sencilla: mientras estén en pantalla, no deben tapar los controles; resérvales espacio o haz que se cierren con una acción simple y que no reaparezcan.

Pruébalo tú

Abre una web con cabecera fija (un periódico o una tienda en línea), baja hasta el pie y recorre la página hacia arriba con Mayús + Tab. Cuenta cuántas veces el foco queda completamente oculto bajo la cabecera. Después, en las herramientas de desarrollo, añade scroll-padding-top al elemento html con la altura de la cabecera y repite: el problema desaparece sin tocar el HTML.

Objetivos de puntero de al menos 24 por 24

2.5.8 Tamaño del objetivo (mínimo) (AA, nuevo en WCAG 2.2) exige que los objetivos de puntero midan al menos 24 por 24 píxeles CSS, salvo excepciones: que tengan espacio suficiente (un círculo de 24 píxeles de diámetro centrado en cada objetivo pequeño no corta a otro objetivo ni a su círculo), que exista un control equivalente que sí cumpla, que estén dentro de un texto (un enlace en una frase), que su tamaño lo decida el navegador sin modificación del autor o que la presentación sea esencial. La versión AAA, 2.5.5 Tamaño del objetivo (mejorado), sube a 44 por 44, una cifra cercana a las recomendaciones de iOS y Android para pantallas táctiles.

Con el tamaño de letra por defecto, 24 píxeles CSS son 1.5rem. Los fallos típicos: botones de icono (cerrar, eliminar), flechas de carrusel, puntos de paginación y enlaces del pie apilados. No hace falta agrandar el dibujo, sino la zona activa.

/* Icono de 16 px dentro de un botón que mide 24 x 24 o más */
.boton-icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  min-height: 1.5rem;
  padding: 0.25rem;
}

/* Ampliar la zona activa sin cambiar el aspecto */
.punto-paginacion { position: relative; width: 0.75rem; height: 0.75rem; }
.punto-paginacion::after {
  content: "";
  position: absolute;
  inset: -0.375rem; /* 12 px + 6 px por cada lado = 24 px */
}

El seudoelemento forma parte del elemento a efectos de clic, así que amplía la zona pulsable. Comprueba que las zonas ampliadas no se solapen con las de los controles vecinos.

Contenido que aparece con el puntero o con el foco

1.4.13 Contenido con hover o foco (AA) se aplica a todo contenido adicional que aparece al pasar el puntero o al enfocar algo (burbujas de ayuda, submenús, vistas previas) y exige tres cosas:

  1. Descartable: se puede cerrar sin mover el puntero ni el foco, normalmente con Escape, salvo que no tape nada ni comunique un error.
  2. Se puede pasar el puntero por encima: si aparece con hover, la persona puede mover el puntero hasta él sin que desaparezca. Quien usa una lupa de pantalla lo necesita para leerlo.
  3. Persistente: sigue visible hasta que se quita el puntero o el foco, se descarta o deja de ser válido.

Una burbuja hecha solo con CSS suele fallar en las tres: aparece únicamente con :hover, deja un hueco entre el disparador y la burbuja por el que «se cae» el puntero y no se cierra con Escape.

/* Incorrecto: solo ratón y con hueco de 8 px */
.ayuda:hover .burbuja { display: block; margin-top: 8px; }

/* Mejor: también con el foco, sin hueco y con una pequeña demora al cerrar */
.burbuja { visibility: hidden; position: absolute; top: 100%; padding-top: 0.5rem;
           transition: visibility 0s linear 300ms; }
.ayuda:hover .burbuja,
.ayuda:focus-within .burbuja { visibility: visible; transition-delay: 0s; }
.ayuda.descartada .burbuja { visibility: hidden; }

El relleno superior (en lugar del margen) hace que la burbuja empiece pegada al disparador, y la demora de 300 milisegundos perdona los movimientos en diagonal, algo que agradecen también los submenús desplegables. La condición «descartable» necesita una línea de JavaScript que añada la clase descartada al pulsar Escape; el patrón completo está en el curso de componentes interactivos. El atributo title queda fuera del criterio porque su burbuja la controla el navegador, pero no es una alternativa: no aparece con el teclado ni en pantallas táctiles.

Punto clave

La interacción se programa en CSS tanto como en JavaScript: outline en :focus-visible con buen contraste, scroll-padding para que ninguna barra fija tape el foco, zonas activas de 24 píxeles CSS como mínimo y contenido emergente que aparezca también con el foco, se pueda recorrer con el puntero y se cierre con Escape.

Resumen

  • Usa :focus-visible con un outline de al menos 2-3 píxeles y contraste de 3:1; no confíes solo en box-shadow.
  • Las cabeceras y barras fijas no pueden tapar el foco (2.4.11): reserva su espacio con scroll-padding y suéltalas con zoom alto.
  • 2.5.8 pide objetivos de 24 por 24 píxeles CSS o espacio equivalente; amplía la zona activa con relleno o seudoelementos.
  • El contenido que aparece con hover o foco debe ser descartable, recorrible con el puntero y persistente (1.4.13).

Comprueba lo que has aprendido

Esta lección se puede leer libremente. Para hacer su test de 3 preguntas, los ejercicios prácticos con páginas reales, guardar tu avance y optar al examen y al certificado, matricúlate en el curso.