Tabs
Tabs
8 reglasUsa tabs solo para contenido paralelo y mutuamente excluyente, nunca para pasos secuenciales
Los tabs sirven para vistas paralelas del mismo objeto donde el usuario elige una a la vez y no necesita comparar las demas: "Resumen", "Detalles", "Reviews" de un producto. No sirven para pasos secuenciales de un wizard (Datos, Pago, Confirmación), donde el orden importa y el tab control deja saltar o retroceder rompiendo el flujo. Tampoco para mezclar navegación entre páginas con tabs in-page: NN/g advierte que combinar ambos contextos en un mismo control desorienta. Si los labels son largos, el contenido es breve, o el contexto es móvil, un accordion suele ser mejor opción que los tabs.
NN/g "Tabs, Used Right" · NN/g "Tabs vs. Accordions"Implementa el patrón ARIA completo: tablist / tab / tabpanel con aria-selected y aria-controls
El patrón oficial WAI-ARIA APG exige tres roles: role="tablist" en el contenedor, role="tab" en cada pestana y role="tabpanel" en cada panel. El tab activo lleva aria-selected="true" y los inactivos aria-selected="false"; cada tab apunta a su panel con aria-controls y cada panel a su tab con aria-labelledby. El tablist necesita un nombre propio vía aria-label o aria-labelledby para que el lector de pantalla lo anuncie al entrar. Un <div onclick> sin roles se lee como texto plano: el usuario de lector de pantalla no sabe que es un tab, cuantos hay ni cual esta seleccionado.
<div role="tablist" aria-label="Cuenta"> <button role="tab" aria-selected="true" aria-controls="p-perfil" id="t-perfil">Perfil</button> </div> <div role="tabpanel" id="p-perfil" aria-labelledby="t-perfil">…</div>
<div class="tab active" onclick="show(0)">Perfil</div> <div class="tab" onclick="show(1)">Seguridad</div> <div class="panel">…</div> <!-- lector: "Perfil Seguridad", texto plano -->
Navega con flechas y activa al foco solo si el panel no tiene latencia
El teclado sigue un patrón preciso: Tab entra al tablist y aterriza en el tab activo, las flechas ← → (o ↑ ↓ en vertical) navegan entre tabs, y Tab de nuevo sale hacia el panel; los tabs inactivos llevan tabindex="-1" (roving tabindex). El APG recomienda la activación automática al recibir foco cuando los paneles se muestran sin latencia perceptible, lo que normalmente exige precargar su contenido. Reserva la activación manual (Enter o Space) para paneles que disparan peticiones de red: si activas en cada movimiento de flecha, cada paso lanza un request y produce flashes y estados inconsistentes.
Ante overflow usa tabs scrollable o un menú "Más", nunca comprimas ni trunces
Cuando los tabs no caben en el ancho disponible hay dos estrategias validas: scrollable tabs (contenedor con overflow-x:auto y sombra o flechas que indican que hay más) o un menú "Más ▾" que agrupa los que no caben. Material 3 recomienda tabs scrollable cuando el número es alto o variable, y reserva los fixed tabs (ancho igual, todos visibles) para conjuntos pequeños y estables. Nunca comprimas los labels hasta hacerlos ilegibles ni los trunces con ellipsis sin tooltip: convertir los tabs en un carrusel ciego reduce la descubribilidad y eleva el costo de interacción.
Usa tabs verticales cuando los labels son largos o las secciones son muchas
Los tabs verticales en columna izquierda resuelven dos problemas que los horizontales no pueden: labels descriptivos que no caben en una fila estrecha y listas largas de secciones que desbordarian cualquier renglon. Son la elección correcta en páginas de ajustes, dashboards y formularios multi-sección con jerarquía profunda; como criterio, más alla de un punado de secciones (LogRocket ubica el rango comodo en torno a 5-7) los horizontales empiezan a truncar. En orientación vertical se usa aria-orientation="vertical" para que las flechas ↑ ↓ sean las que naveguen entre tabs en lugar de ← →.
Perfil
Labels completos y legibles, indicador izquierdo de 3px en la sección activa, y el rail no se desplaza al cambiar de panel. Las flechas ↑ ↓ navegan con aria-orientation="vertical".
Badge de conteo: oculta el número al lector pero incluyelo en el aria-label del tab
Un badge de conteo sobre un tab necesita doble tratamiento de accesibilidad: el elemento visual lleva aria-hidden="true" para que el lector de pantalla no lea el número como texto suelto, y ese conteo se incorpora al aria-label del tab completo ("Mensajes, 5 sin leer"). Cuando el conteo cambia en vivo, anuncialo desde una region aria-live="polite" sin mover el foco. Nunca comuniques el significado solo con color: un punto rojo sin número ni texto alternativo es invisible para lectores de pantalla y para personas con daltonismo, en contra de WCAG 1.4.1 Use of Color.
badge aria-hidden="true" · cambios vía aria-live="polite"
El tab activo pide dos señales visuales; el foco de teclado, 3:1 de contraste y 2px
El tab seleccionado nunca debe distinguirse solo por color: usa al menos dos indicadores simultaneos (subrayado + negrita, color + borde inferior, fondo + peso tipografico). En tabs el foco de teclado y la selección son estados distintos, así que el indicador de foco es critico y debe cumplir WCAG 2.4.11 (AA): contraste mínimo 3:1 entre el estado con foco y sin foco, y un área equivalente al perimetro de un trazo de 2px CSS alrededor del componente. Un outline:2px solid con offset cumple el mínimo; si es punteado o discontinuo hay que duplicar el grosor para compensar los huecos. Jamas outline:none sin reemplazo visible.
En móvil limita los tabs visibles, soporta swipe entre paneles y respeta el target tactil mínimo
En pantallas pequeñas muestra pocos tabs a la vez (como criterio, 3-5); si hay más, activa scroll horizontal con indicador de que hay contenido lateral, y nunca recurras a tabs verticales: la superficie es insuficiente. El gesto de swipe horizontal debe mover el panel (no los tabs) y mantenerse sincronizado con la tab activa. El área tactil de cada tab debe respetar el mínimo normativo de WCAG: 24×24px CSS por SC 2.5.8 Target Size (Minimum, AA), idealmente 44×44px por SC 2.5.5 (AAA); para labels chicos se expande el área de toque invisible, no se reduce el texto. El "48px" de los blogs de UI es una recomendación comoda, no la norma a citar.
W3C WCAG 2.2 SC 2.5.8 Target Size (Minimum) · SC 2.5.5 Target Size (Enhanced) · Material Design 3 Tabs (scroll horizontal) · LogRocket (criterio 3-5)- R-483 Usa tabs solo para contenido paralelo y mutuamente excluyente, nunca para pasos secuenciales
- R-484 Implementa el patrón ARIA completo: tablist / tab / tabpanel con aria-selected y aria-controls
- R-485 Navega con flechas y activa al foco solo si el panel no tiene latencia
- R-486 Ante overflow usa tabs scrollable o un menú "Más", nunca comprimas ni trunces
- R-487 Usa tabs verticales cuando los labels son largos o las secciones son muchas
- R-488 Badge de conteo: oculta el número al lector pero incluyelo en el aria-label del tab
- R-489 El tab activo pide dos señales visuales; el foco de teclado, 3:1 de contraste y 2px
- R-490 En móvil limita los tabs visibles, soporta swipe entre paneles y respeta el target tactil mínimo