Focus visible y teclado: el 90% de los bugs de accesibilidad están aquí

Ilustración con estilo editorial que muestra una ventana de navegador y navegación por teclado: un botón “Saltar al contenido principal”, tarjetas y un botón “Ver detalle” con contornos de foco resaltados. Alrededor aparecen iconos de Tab, Skip links, Cards clicables y una alerta de “trampas típicas”, junto al título “Focus visible y teclado: el 90% de los bugs de accesibilidad están aquí” y la URL martagonzalez.dev.

Si tu interfaz “se ve bien” pero al navegar con Tab se vuelve un laberinto, no es mala suerte: es el patrón más repetido de bugs de accesibilidad. Y lo peor es que suele pasar incluso en equipos con buen nivel técnico, porque el teclado no se “prueba” con la misma seriedad que el responsive o el rendimiento.

En este artículo vamos a atacar el núcleo: focus visible, orden de tab, skip links, cards clicables y las trampas típicas (modales, menús, overlays, estados, formularios). Te lo cuento de tú a tú, con tono profesional y con ejemplos de diseño e interacción que puedes aplicar hoy.

Importante: muchas auditorías automáticas te dirán “casi ok”. Pero el teclado se valida con manos humanas. Y ahí aparecen los fantasmas.

Por qué el foco es el epicentro (y por qué te rompe la UX si falla)

Cuando navegas con ratón, tu “cursor” es obvio. Con teclado, el cursor es el foco. Si no ves dónde estás, la interfaz deja de ser una interfaz: es una adivinanza.

Aquí es donde entra la comparación clave: tiempo de decisión vs. carga cognitiva.

  • Tiempo de decisión: cuánto tardas en decidir “¿qué hago ahora?”.
  • Carga cognitiva: cuánta energía mental gastas para entender “¿dónde estoy, qué puedo hacer, qué va a pasar?”.

Un focus visible claro reduce ambos:

  • Te ubica en milisegundos (menos tiempo de decisión).
  • Evita que tengas que “reconstruir” el contexto mentalmente (menos carga cognitiva).

Cuando el foco no se ve o el orden de tabulación es caótico, obligas a la persona a:

  1. buscar (¿dónde está el foco?),
  2. inferir (¿qué elemento es este?),
  3. corregir (me pasé, vuelvo con Shift+Tab),
  4. dudar (¿si pulso Enter… rompo algo?).

Y eso en UI complejas (dashboards, filtros, cards, menús) es un multiplicador de frustración.

:focus vs :focus-visible (y cómo dejar de pelearte con los estilos)

Qué es exactamente :focus-visible

  • :focus se activa cuando un elemento recibe foco, venga de teclado, mouse o script.
  • :focus-visible intenta mostrarse solo cuando tiene sentido para la persona usuaria, típicamente cuando navega con teclado.

La consecuencia práctica: puedes diseñar un anillo de foco potente para teclado sin que “moleste” a quien usa ratón.

Regla de oro de CSS (y el pecado mortal del outline: none)

Si has visto esto en un proyecto, es una red flag:

*:focus {
outline: none;
}

Eso es como apagar el foco. Y sí: mucha gente lo hace “para que no se vea feo”. El resultado es que tu UI se vuelve inaccesible por teclado.

La alternativa moderna y segura:

/* Base: dejamos el outline por defecto como fallback */
:focus {
outline: 2px solid transparent;
outline-offset: 2px;
}/* Teclado: anillo visible y consistente */
:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}/* Opcional: si usas Tailwind, esto suele mapear a ring utilities */

Consejo pro: usa outline (no box-shadow) como primera opción. outline no afecta layout y suele ser más consistente.

El anillo de foco no es “decoración”, es señalización

Un buen focus visible tiene:

  • Contraste suficiente contra el fondo (y también contra el propio componente).
  • Grosor visible (no “una línea finita” que desaparece).
  • Offset para que no se confunda con el borde del botón.
  • Consistencia: mismo estilo en botones, links, inputs, chips, cards.

El problema de “no se ve en fondos con imagen / gradiente”

Si tu UI tiene hero con imagen, tarjetas con fotos o fondos con ruido visual, el foco necesita doble capa:

:focus-visible {
outline: 3px solid #fff;
outline-offset: 3px;
box-shadow: 0 0 0 6px rgba(0,0,0,.6);
}

No es para “embellecer”: es para que el foco sobreviva en fondos complejos.

Orden de tab: si el DOM y el layout no cuentan la misma historia, pierdes

El orden de tabulación sigue el orden del DOM (con matices), no el orden visual de tu grid. Por eso, cuando haces layouts con CSS (grid, flex, reorder), puedes crear un recorrido por teclado completamente surrealista.

Las reglas que casi nunca fallan

  1. No uses tabindex positivo (tabindex="1", 2, 3"...).
    • Es un imán de bugs.
    • Rompe expectativas y se vuelve inmantenible.
  2. Mantén el orden lógico en el DOM igual que el visual (siempre que puedas).
  3. Si necesitas reordenar visualmente, piensa si estás creando dos narrativas: una visual y otra para teclado/lectores.
  4. Evita esconder elementos focusables con display:none o visibility:hidden de forma que cambie el orden inesperadamente (especialmente en menús responsive).

El caso típico: filtros + resultados + sidebar

En un e-commerce o listado con filtros:

  • Lo visual suele ser: filtros a la izquierda, resultados a la derecha.
  • Pero si en móvil pones filtros arriba y luego en desktop los “mueves” visualmente sin cambiar el DOM, el tab puede ir: resultados → header → filtros → footer… un caos.

Solución recomendada: estructura DOM con un orden lógico y usa CSS para adaptarlo sin “teletransportar” secciones críticas.

Las skip links (enlace para “saltar al contenido”) son ese detalle que parece pequeño… hasta que tienes que tabular por:

  • logo,
  • navegación,
  • buscador,
  • botones,
  • banners,
  • cookie bar,
  • etc.

Cómo se implementa bien

En el primer foco de la página (normalmente el primer elemento del body), añade:

<a class="skip-link" href="#main-content">Saltar al contenido principal</a><header>...</header><main id="main-content" tabindex="-1">
...
</main>What is this?

Y el CSS clásico (oculta pero accesible, aparece al enfocar):

.skip-link {
position: absolute;
left: -999px;
top: 1rem;
padding: .75rem 1rem;
background: #fff;
border: 2px solid #000;
z-index: 9999;
}.skip-link:focus-visible {
left: 1rem;
}

Por qué tabindex="-1" en main: permite que el salto de foco sea confiable incluso si el navegador no enfoca el destino como esperas.

Si además usas landmarks (<header>, <nav>, <main>, <footer> y/o role="navigation"), ayudas a que la navegación sea más rápida en lectores de pantalla. No es “solo teclado”, es arquitectura.

Cards clicables: el patrón favorito de los bugs silenciosos

Las cards clicables son bonitas. Y peligrosas.

El problema típico:

  • Haces una card con div y onClick.
  • Visualmente parece un link.
  • Con teclado no es interactiva.
  • O lo es “a medias” con tabIndex=0 y role=button, pero luego Enter no navega o Space hace scroll.
  • Y si dentro hay links reales, creas elementos interactivos anidados (error UX + accesibilidad).

Lo más limpio:

<a class="card" href="/detalle">
<h3>Título</h3>
<p>Descripción</p>
</a>What is this?

Ventajas:

  • Tab funciona.
  • Enter funciona.
  • Semántica correcta.

Peeero: solo si la card realmente representa una única acción (ir al detalle).

Si la card tiene botones dentro (guardar, compartir, etc.), no conviertas todo en link. Haz esto:

<article class="card">
<h3>
<a href="/detalle">Título</a>
</h3> <p>Descripción</p> <div class="actions">
<button type="button">Guardar</button>
<button type="button">Compartir</button>
</div>
</article>What is this?

Y si quieres que al clicar en el “fondo” también navegue, hazlo con cuidado: sin romper teclado y sin anidar interactivos. Un truco visual común es usar un pseudo-elemento para ampliar el área clicable del link del título (sin envolverlo todo en un <a>).

Trampa típica — “div role=button” como solución rápida

Sí, se puede hacer accesible… pero es más fácil romperlo que hacerlo bien. Si aun así lo haces:

  • gestiona Enter y Space,
  • añade aria-label si no hay texto,
  • asegura estilos de foco,
  • y recuerda que un “botón que navega” suele ser peor que un link.

Formularios accesibles: foco, errores y mensajes que no se pierden

Ya que la keyword secundaria incluye formularios accesibles, aquí es donde el foco se vuelve crítico.

Error summary + foco al primer error

Cuando envías un formulario con errores:

  1. Muestra un resumen arriba (“Revisa los campos marcados”).
  2. Lleva el foco a ese resumen.
  3. Desde ahí, ofrece links a cada campo con error.

Ejemplo conceptual:

<div class="error-summary" role="alert" tabindex="-1" id="error-summary">
<p><strong>Hay errores en el formulario</strong></p>
<ul>
<li><a href="#email">El email no es válido</a></li>
<li><a href="#password">La contraseña es obligatoria</a></li>
</ul>
</div>What is this?

Y al fallar validación:

document.getElementById("error-summary")?.focus();

aria-describedby para unir campo + ayuda + error

<label for="email">Email</label>
<input id="email" aria-describedby="email-hint email-error" /><p id="email-hint">Usa un correo real. Te enviaremos confirmación.</p>
<p id="email-error" role="status">Formato incorrecto.</p>What is this?

Clave: si el error aparece dinámicamente, asegúrate de que se anuncie y de que el foco no “salte” sin motivo.

Trampas típicas con teclado (las que más veo en UI reales)

Modales sin focus trap (o con focus trap agresivo)

Un modal accesible necesita:

  • foco inicial dentro (idealmente en el título o el primer control),
  • focus trap para que Tab no se vaya detrás,
  • cierre con Escape,
  • restaurar el foco al elemento que lo abrió.

Solución moderna: inert para el fondo

Cuando el modal está abierto, el resto de la página debería quedar “inactivo”. inert ayuda mucho (según soporte del navegador / polyfill si lo necesitas):

const main = document.querySelector("main");
main?.setAttribute("inert", "");
// al cerrar:
main?.removeAttribute("inert");

Menús desplegables que se cierran al perder foco

Si tu dropdown se cierra cuando “blur” ocurre, puedes crear un infierno:

  • tabulas al siguiente item y se cierra antes de poder interactuar,
  • o se cierra al intentar usar flechas.

Solución: define claramente el patrón (menú tipo navegación vs select vs combobox). Y si no estás segura, apóyate en patrones ARIA bien establecidos (y pruébalo con teclado real).

“Scroll jail”: foco dentro de contenedores con overflow

Cuando metes listas scrollables (overflow:auto) y no gestionas bien el foco:

  • el foco se mueve pero la lista no hace scroll para mostrarlo,
  • parece que “desapareció”.

Solución: al cambiar foco dentro de un contenedor scrollable, asegúrate de que el elemento se vea (element.scrollIntoView({ block: "nearest" }) con moderación).

Checklist de supervivencia: prueba manual que detecta el 90% de fallos

Recorrido mínimo (5 minutos por pantalla)

  1. Tab desde el inicio: ¿aparece focus visible siempre?
  2. ¿El foco se ve en botones, links, inputs, chips, icon buttons?
  3. ¿El orden de tab es lógico y coincide con el recorrido visual?
  4. ¿Hay skip link? ¿Funciona?
  5. ¿Puedes activar todo con Enter y/o Space cuando corresponde?
  6. ¿Hay algún “callejón sin salida” (no puedes salir de un componente)?
  7. En formularios: ¿al error te enteras, y sabes qué campo corregir?

Antipatrones que debes cazar como si fueran pokémon

  • outline: none sin alternativa.
  • tabindex="1" (o cualquier positivo).
  • Cards enteras clicables con div onClick sin semántica.
  • Modales que abren y el foco se queda detrás.
  • Componentes “bonitos” que solo funcionan con mouse.

Enlaces internos recomendados (para hilar la serie)

Para completar el mapa mental y reforzar SEO interno, enlaza desde aquí a:

  • Links accesibles: “haz click aquí” es un crimen → (añade tu URL final)
    Sugerencia de slug: /blog/links-accesibles-haz-click-aqui-es-un-crimen/
  • Componentes UI accesibles (patrones) → (añade tu URL final)
    Sugerencia de slug: /blog/componentes-ui-accesibles-patrones/

(Tip SEO: enlaza también de vuelta desde esos posts hacia este, usando el anchor “focus visible” o “navegación con teclado”.)

FAQs (preguntas frecuentes)

1) ¿focus-visible funciona en todos los navegadores?

En la mayoría de navegadores modernos, sí. Aun así, conviene dejar un fallback con :focus para no quedarte sin estilos si algo falla. La estrategia habitual es: focus como base + focus-visible como mejora.

2) ¿Por qué no debería usar tabindex positivo para “arreglar” el orden?

Porque es pan para hoy y deuda técnica para mañana. En cuanto cambias algo del DOM, el orden se vuelve impredecible y cuesta muchísimo mantenerlo. Lo correcto es arreglar el orden en el DOM y reservar tabindex para casos muy concretos (0 o -1).

3) ¿Cómo hago una card clicable sin romper accesibilidad?

Piensa primero si la card representa una única acción. Si sí, usa un <a> envolviendo el contenido. Si no (porque hay botones dentro), usa link principal (título) + acciones separadas. Evita anidar interactivos y evita div clicables como atajo.


Accesibilidad es bajar el volumen mental

Un buen focus visible no es un “detalle de a11y”. Es un contrato de claridad: “estás aquí, puedes hacer esto, y esto es lo que pasará”. Cuando ese contrato se rompe, sube el tiempo de decisión y explota la carga cognitiva. Y eso no afecta solo a personas con discapacidad: afecta a cualquiera que navegue rápido, con prisa, con fatiga, con una mano ocupada, o simplemente con preferencia por teclado.

Si quieres un criterio simple para priorizar: si una pantalla funciona perfecta con teclado, suele funcionar mejor para todo el mundo. Y además, te obliga a diseñar interacciones más honestas: menos trucos visuales, más intención semántica, más estructura.

La próxima vez que alguien diga “esto es solo accesibilidad”, prueba a responder:
“No: esto es usabilidad bajo presión.” Y el teclado es la prueba de fuego.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *