Cómo crear tabs accesibles con Bootstrap o Tailwind

Cómo crear tabs accesibles con Bootstrap y Tailwind
Solucionex
10
Nov 25

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

  1. No usar roles ARIA adecuados → Sin tablist, tab, tabpanel, el lector de pantalla no entiende la relación entre pestañas y contenido.

  2. No gestionar el foco → El usuario no sabe en qué pestaña está.

  3. Navegación de teclado incorrecta → Se debe poder usar ← → para moverse entre pestañas y Enter o Space para activarlas.

  4. Paneles ocultos mal gestionados → El contenido inactivo debe estar oculto semánticamente (hidden o aria-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-selected indica 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.

Bootstrap
tailwindcss
Accesibilidad Web
Frontend