Las pestañas (tabs) son un patrón de interfaz muy usado para mostrar distintos contenidos dentro de la misma página sin recargarla. Sin embargo, si no se implementan correctamente, pueden ser un gran problema de accesibilidad web: usuarios de teclado que no pueden cambiar de tab, lectores de pantalla que no saben qué panel está activo, etc.
En este post vamos a ver cómo construir tabs accesibles, tanto con Bootstrap (que ya ofrece un componente nativo) como con Tailwind (donde tendremos que añadir la lógica).
Problemas comunes de accesibilidad web en tabs
No usar roles ARIA adecuados → Sin
tablist,tab,tabpanel, el lector de pantalla no entiende la relación entre pestañas y contenido.No gestionar el foco → El usuario no sabe en qué pestaña está.
Navegación de teclado incorrecta → Se debe poder usar
← →para moverse entre pestañas yEnteroSpacepara activarlas.Paneles ocultos mal gestionados → El contenido inactivo debe estar oculto semánticamente (
hiddenoaria-hidden="true").
Tabs accesibles con Bootstrap 5
Bootstrap nos da nav-tabs, pero hay que asegurarse de usar los atributos ARIA:
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="home-tab" data-bs-toggle="tab"
data-bs-target="#home" type="button" role="tab"
aria-controls="home" aria-selected="true">
Inicio
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab"
data-bs-target="#profile" type="button" role="tab"
aria-controls="profile" aria-selected="false">
Perfil
</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="home" role="tabpanel"
aria-labelledby="home-tab">
Contenido de Inicio
</div>
<div class="tab-pane fade" id="profile" role="tabpanel"
aria-labelledby="profile-tab">
Contenido de Perfil
</div>
</div>👉 Bootstrap gestiona aria-selected, aria-controls, y las clases de visibilidad automáticamente.
Tabs accesibles con Tailwind
En Tailwind hay que implementar la lógica. Ejemplo de dos pestañas:
<div class="w-full max-w-md mx-auto" aria-label="Ejemplo de pestañas">
<!-- Lista de tabs -->
<div role="tablist" aria-orientation="horizontal" class="flex border-b">
<button id="tab-1" role="tab" aria-controls="panel-1" aria-selected="true"
class="tab-btn px-4 py-2 border-b-2 border-blue-500 focus:outline-none">
Tab 1
</button>
<button id="tab-2" role="tab" aria-controls="panel-2" aria-selected="false"
class="tab-btn px-4 py-2 border-b-2 border-transparent focus:outline-none">
Tab 2
</button>
</div>
<!-- Paneles -->
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1" class="tab-panel p-4">
Contenido de la pestaña 1
</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" class="tab-panel p-4 hidden">
Contenido de la pestaña 2
</div>
</div>
<script>
const tabs = document.querySelectorAll(".tab-btn");
const panels = document.querySelectorAll(".tab-panel");
tabs.forEach((tab, index) => {
tab.addEventListener("click", () => {
// resetear todos
tabs.forEach(t => {
t.setAttribute("aria-selected", "false");
t.classList.remove("border-blue-500");
t.classList.add("border-transparent");
});
panels.forEach(p => p.classList.add("hidden"));
// activar el seleccionado
tab.setAttribute("aria-selected", "true");
tab.classList.add("border-blue-500");
document.getElementById(tab.getAttribute("aria-controls")).classList.remove("hidden");
});
// navegación con flechas
tab.addEventListener("keydown", (e) => {
if (e.key === "ArrowRight") {
e.preventDefault();
tabs[(index + 1) % tabs.length].focus();
} else if (e.key === "ArrowLeft") {
e.preventDefault();
tabs[(index - 1 + tabs.length) % tabs.length].focus();
}
});
});
</script>👉 Claves de esta implementación:
role="tablist",role="tab",role="tabpanel"aseguran semántica correcta.aria-selectedindica qué tab está activa.Se puede navegar con
Tab,←y→.Los paneles inactivos se ocultan con
hidden.
Conclusión
Las tabs son un patrón muy extendido y existe un estándar WAI-ARIA muy claro para implementarlas.
Con Bootstrap gran parte del trabajo está hecho, solo hay que seguir la estructura.
Con Tailwind, hay que construir la lógica de activación, pero siguiendo los roles y atributos ARIA, conseguimos tabs 100% accesibles.