Barra de navegación con Tailwind CSS y menú móvil

Autor: Pablo Mayoral Publicado: 07/11/2026 Actualizado: 07/11/2026

Usa los mismos destinos en móvil y escritorio. Conecta el botón con el panel y comprueba cómo se conserva el foco cuando cambia el ancho de la cabecera.

Barra de navegación con Tailwind CSS y menú móvil
Resumen Directo

Una barra adaptable con Tailwind CSS usa una lista semántica y un botón que la despliega en espacios estrechos. Elige el punto de corte con los rótulos reales y prueba el foco al cerrar el panel o cambiar de ancho.

Para crear una barra de navegación adaptable con Tailwind CSS, usa una sola lista de enlaces y preséntala de dos formas. Cuando haya espacio, coloca los destinos en fila. En una pantalla estrecha, deja que un botón muestre la misma lista en un panel que desplace el contenido hacia abajo. Tailwind resuelve la distribución y un controlador pequeño sincroniza la visibilidad, el estado del botón y el foco. Deja los enlaces visibles en el HTML inicial para que la navegación funcione aunque falle la carga del controlador.

Parte de una sola lista de destinos

Imagina Fieldnote, un sitio ficticio para equipos que investigan sobre el terreno. La navegación principal lleva a Proyectos, Guías de trabajo de campo y Ayuda. El enlace de la marca lleva al inicio y el de la cuenta abre el espacio de trabajo. Decide si la cuenta es suficientemente importante para seguir a la vista en móvil. En este ejemplo, el acceso se conserva en la lista y queda disponible en ambos anchos.

La navegación contiene una lista de enlaces. El enlace de Proyectos tiene una marca de página actual.
La lista fija el orden de destinos y el estado de página actual pertenece al enlace correspondiente.

Usa un elemento nav para la navegación principal, una lista para agrupar las opciones y enlaces con URL reales para los destinos. Asigna aria-current="page" al enlace de la página actual y acompáñalo de una señal visible, como el subrayado. Si también hay navegación en el pie de página, dale un nombre que la distinga de la principal. El estándar HTML para la navegación define este elemento y el ejemplo de navegación de WAI muestra el estado de página actual junto a un control que despliega enlaces.

El botón abre y cierra el panel. Cada enlace lleva a otra página. Mantén clara esa distinción aunque el producto llame «menú» a toda la barra. El ejemplo de WAI emplea enlaces normales y explica las interacciones adicionales que se esperan de un widget con el rol ARIA de menú. Con enlaces normales, la persona puede recorrer la navegación con Tab igual que el resto de la página.

Elige el punto de corte con textos reales

Empieza por la lista vertical y pasa a la fila cuando quepan todos los elementos con holgura. Cuenta el ancho de la marca, el texto traducido, el relleno de los enlaces y el hueco antes del acceso a la cuenta. En Fieldnote, prueba «Guías de trabajo de campo» con la tipografía final. Un boceto con «Guías» puede hacer que todo parezca encajar hasta que se incorporan los textos definitivos.

La barra amplia de Fieldnote muestra los destinos en fila y la compacta reserva espacio para el botón de menú.
Prueba el rótulo traducido completo antes de fijar el paso a la fila horizontal.

En el ejemplo, md:flex-row cambia la dirección de la lista y md:flex-wrap permite que un elemento salte de línea. El md predeterminado de Tailwind empieza en 48rem. Úsalo como referencia inicial. Si el proyecto cambia el punto de corte, actualiza también la consulta del controlador. Cuando la barra se monta en un contenedor estrecho dentro de una ventana amplia, comprueba el ancho real disponible. Una consulta de ventana no mide el espacio que le deja una columna lateral. Consulta la documentación de diseño adaptable de Tailwind y la guía sobre flex y desbordamientos.

Prueba la fila justo por debajo y por encima de su punto de corte. Después amplía el texto y comprueba que se pueda llegar al acceso a la cuenta sin desplazamiento horizontal. Una segunda fila puede funcionar si la cabecera admite más altura. Si partir la navegación dificulta encontrar las opciones, retrasa el paso a la fila. Puedes reproducir la prueba con la guía de WCAG sobre reajuste del contenido y el ejercicio de tipografía y zoom con Tailwind.

Conecta el botón con el panel

Deja la lista visible en el HTML inicial. Aplica las clases de distribución a la lista y reserva hidden para el contenedor del panel. Así, un único elemento determina la visibilidad. Una regla de distribución en el propio contenedor puede prevalecer sobre el estado oculto. Evita, por ejemplo, aplicar flex directamente al elemento que recibe hidden. El estándar HTML describe este efecto de hidden. Las utilidades de display de Tailwind ayudan a seguir esa diferencia en el inspector.

El botón comunica false al cerrar y true al abrir. La barra amplia muestra la lista y oculta el botón.
Una sola lista alimenta los tres estados. El botón compacto controla la visibilidad.

El marcado abreviado de abajo corresponde a la página Proyectos. Si montas más de una instancia en la misma página, usa identificadores distintos. El botón compacto arranca oculto y los enlaces se muestran. Cuando carga el controlador, muestra el botón y recoge el panel en el ancho estrecho. El patrón de contenido desplegable de WAI detalla la activación y el significado de su estado expandido.

<nav id="site-nav" aria-label="Principal"
     class="grid gap-4 p-4 md:grid-cols-[auto_1fr] md:items-center">
  <div class="flex items-center justify-between gap-4">
    <a href="/es/">Fieldnote</a>
    <button type="button" id="nav-toggle" hidden
            aria-expanded="false" aria-controls="nav-panel"
            class="min-h-[44px] rounded-xl border px-4">Menú</button>
  </div>
  <div id="nav-panel">
    <ul class="flex flex-col gap-2 md:flex-row md:flex-wrap md:justify-end">
      <li><a href="/es/proyectos" aria-current="page"
             class="block rounded-xl px-3 py-2 underline">Proyectos</a></li>
      <li><a href="/es/guias" class="block rounded-xl px-3 py-2">Guías de trabajo de campo</a></li>
      <li><a href="/es/ayuda" class="block rounded-xl px-3 py-2">Ayuda</a></li>
      <li><a href="/es/cuenta" class="block rounded-xl px-3 py-2">Cuenta</a></li>
    </ul>
  </div>
</nav>

El nombre visible «Menú» se mantiene al abrir y al cerrar. El estado expandido comunica el cambio y el atributo de control identifica el panel. Deja una superficie cómoda para pulsar el botón y suficiente separación respecto de los controles contiguos. La guía de tamaño mínimo de objetivos de WCAG 2.2 explica el mínimo de 24 píxeles CSS, la separación y las excepciones. Para este ejemplo se ha elegido una altura mínima de 44 píxeles CSS y relleno horizontal.

Conserva el foco al cambiar de ancho

Al abrir, el foco se queda en el botón. Tab entra después en la lista según el orden del documento. Escape desde el panel abierto lo cierra y devuelve el foco al botón. Cuando el foco está en otra parte de la página, deja que Escape conserve su función allí. El panel se despliega dentro de la cabecera y el resto de la página sigue disponible, sin fondo modal ni captura del foco.

Dos cambios de ancho muestran cómo el foco pasa del botón a un enlace o sigue en el enlace al abrirse el panel compacto.
Lee el foco antes de cambiar la visibilidad. Muévelo si va a desaparecer su control.

Ante un cambio de ancho, mira qué elemento tiene el foco antes de ocultar controles. Cuando se pasa a escritorio y el foco está en el botón, muévelo al enlace actual o al primero disponible. Al volver a la vista compacta con un enlace enfocado, mantén el panel abierto para conservar ese enlace a la vista. Si el foco estaba fuera de la navegación, recoge el panel y deja el foco en su sitio. Así, alguien que ajusta la ventana a mitad de una tarea puede continuar desde el mismo lugar.

const nav = document.querySelector("#site-nav");
const toggle = nav.querySelector("#nav-toggle");
const panel = nav.querySelector("#nav-panel");
const wide = matchMedia("(min-width: 48rem)");

function setPanel(open) {
  panel.hidden = !open;
  toggle.setAttribute("aria-expanded", String(open));
}

function syncLayout() {
  const active = document.activeElement;
  if (wide.matches) {
    setPanel(true);
    if (active === toggle) {
      (panel.querySelector('[aria-current="page"]') || panel.querySelector("a")).focus();
    }
    toggle.hidden = true;
    return;
  }
  toggle.hidden = false;
  setPanel(panel.contains(active));
}

toggle.addEventListener("click", () => setPanel(panel.hidden));
nav.addEventListener("keydown", (event) => {
  if (event.key !== "Escape" || wide.matches || panel.hidden) return;
  if (event.target !== toggle && !panel.contains(event.target)) return;
  event.preventDefault();
  toggle.focus();
  setPanel(false);
});
wide.addEventListener("change", syncLayout);
syncLayout();

Carga el controlador después del marcado, por ejemplo con un script diferido en las páginas que incluyen la cabecera. Documenta juntos los dos valores del punto de corte. Si una aplicación monta y desmonta la barra, registra los eventos al montarla y retíralos al desmontarla. En una página convencional con una sola barra basta la inicialización del ejemplo.

Si la aplicación cambia de página sin recargar, coordina el cierre con la política de foco del router. Si este coloca el foco en el titular de la nueva página, cierra el panel anterior después de moverlo. En una carga normal, llega un documento nuevo y el controlador se inicializa de nuevo. Evita cerrar al salir el puntero del panel. Una persona puede apartar el ratón mientras lee, y una pantalla táctil tampoco depende de que el puntero permanezca encima.

Revisa las barras de EmviUI en ambos anchos

El kit de Figma de EmviUI ofrece muestras de navegación móvil con marca y botón de hamburguesa, además de muestras de escritorio que cambian la posición del grupo de enlaces respecto de una acción destacada. La muestra compacta sirve para decidir dónde colocar el botón. La de escritorio permite comparar el equilibrio entre alinear los enlaces a la izquierda, centrarlos o acercarlos a la acción cuando introduces los textos del sitio.

Dos cabeceras compactas de EmviUI con marca y botón de hamburguesa, una con un icono de acciones adicional
Muestras de barras compactas de EmviUI para decidir dónde colocar la marca y el botón de apertura.

En Fieldnote, parte de la variante compacta más sencilla, con la marca a la izquierda y el botón a la derecha. Conserva el nombre accesible «Menú» si la versión visual usa solo el icono. Dibuja una vista con el panel desplegado y otra con el foco en Ayuda. Anota en el archivo el orden de los destinos y si el panel empuja el contenido. El kit aporta una referencia visual compartida. Comprueba los estados y el comportamiento del foco en la cabecera implementada.

Distingue el indicador de página actual del indicador de foco. Una persona puede estar en Proyectos y haber llegado a Ayuda con Tab. Subraya Proyectos y dibuja el contorno de foco alrededor de Ayuda para que se puedan leer ambos estados a la vez. Consulta la guía de foco visible de WCAG y el artículo sobre color, foco y estados con Tailwind. Si el producto tiene cabeceras claras y oscuras, prueba el contorno en ambos fondos.

Comprueba el teclado y el cambio de tamaño

Monta la barra en una página que tenga suficiente contenido para desplazarse y añade un control enfocable justo después. Haz una pasada sin JavaScript, otra con ratón o pantalla táctil y otra solo con teclado. Inspecciona el árbol de accesibilidad con el panel abierto y cerrado. En compacto y cerrado, los enlaces del panel deben quedar fuera del recorrido de Tab y el botón debe comunicar que está contraído.

El recorrido de teclado va del botón al primer enlace con Tab y vuelve al botón al pulsar Escape.
Abre con Intro o Espacio. Pulsa Tab para entrar y Escape para comprobar que el foco vuelve.
PruebaResultado esperado
Desactiva JavaScript en ancho estrechoLos enlaces siguen visibles y el botón permanece oculto.
Activa el botón con Intro y EspacioAmbas teclas abren el panel y el foco se queda en el botón.
Pulsa Escape desde un enlace del panelEl panel se cierra y el foco vuelve al botón.
Ensancha con el foco en el botónEl foco pasa al enlace actual o al primero de la lista.
Estrecha con el foco en un enlaceEl panel sigue abierto y se conserva el enlace enfocado.
Estrecha con el foco en el contenido principalEl panel se recoge y el foco se queda en el contenido.

Registra el ancho medido, el idioma, el zoom y el elemento enfocado en cada fallo. Si aparece desplazamiento horizontal, revisa primero los rótulos largos y el ancho de su contenedor. Si Tab entra en el panel recogido, comprueba el estado en el DOM y el valor calculado de display. Si Escape cierra el panel pero el foco se pierde, averigua si el botón ya estaba oculto al intentar enfocarlo. Una misma captura puede esconder tres causas distintas.

Repite las comprobaciones en la versión de producción con el rótulo más largo. Si la cabecera queda fija al desplazar la página, comprueba que el primer control situado debajo siga visible al recibir el foco. Termina el recorrido abriendo Ayuda desde la vista compacta y pulsando Atrás. La página de destino debe cargar y, al volver, la barra debe seguir siendo utilizable. Esa prueba conecta el aspecto de la cabecera con la tarea que tiene que resolver.

Entrega la navegación con sus reglas de uso

Entrega un único inventario con la ruta, el rótulo por idioma, la posición y la condición para marcar la página actual. Describe aparte los tres estados que aparecen en los diagramas. Con una sola lista de destinos, una actualización de nombre o URL llega a los dos diseños. Añade una captura tomada justo por encima del punto de corte y con los rótulos reales para que otra persona pueda reproducir el caso que motivó el cambio de distribución.

Acuerda una prueba de fallo antes de cerrar la revisión visual. Sustituye «Ayuda» por «Asistencia técnica para equipos de investigación» y abre el sitio con un ancho compacto y texto ampliado. Despliega los enlaces, llega a ese rótulo con Tab, ensancha la ventana y vuelve a estrecharla. El texto largo puede ocupar dos líneas y la cabecera puede crecer. El destino debe seguir legible y el mismo enlace debe conservar el foco durante ambos cambios. Guarda el resultado observado junto a las vistas estáticas de Figma.

Para la revisión, una persona puede manejar el teclado y otra observar el DOM. Tras el primer cambio de ancho, pregunta qué control espera activar quien está probando si pulsa Intro. Contrasta su respuesta con el indicador visible y el elemento activo del navegador. Después repite con el puntero fuera de la cabecera para distinguir el foco de los estilos que se activan al pasar el ratón. Si la cabecera se reutiliza en otra página, comprueba que el atributo de página actual aparezca únicamente en el enlace que corresponde a esa ruta. La lista compartida tiene que reflejar la página desde la que se muestra.

Pablo Mayoral

Diseñador UX/UI y Web con más de 10 años de experiencia. Creador de EmviUI (sistema de diseño en Figma con más de 10k componentes), UImand y Vitamin Bootstrap. Diseñando actualmente productos digitales en CQMRewards.

Preguntas frecuentes

¿Cómo hago una barra de navegación adaptable con Tailwind CSS? ▼
Empieza con una lista vertical y pásala a una fila cuando quepa el contenido real. En estrecho, un botón muestra esa misma lista. Tailwind cambia la distribución y el controlador se ocupa de la visibilidad.
¿La navegación del sitio necesita el rol ARIA de menú? ▼
Para destinos normales puedes usar enlaces en un elemento de navegación. El ejemplo de WAI prescinde del rol de menú, que implica interacciones adicionales. Un botón permite desplegar los enlaces.
¿Puedo aplicar hidden y flex al mismo panel? ▼
Colócalos en elementos distintos. Un valor explícito de display puede prevalecer sobre el estado HTML oculto. Aplica la distribución a la lista y el estado hidden al contenedor que la envuelve.
¿Qué debe hacer Escape en la barra móvil? ▼
En este diseño, Escape cierra el panel abierto si el foco está en el botón o dentro del panel y devuelve el foco al botón. Si el foco está en otra zona, la barra no interviene.
¿Hay que encerrar el foco en el panel? ▼
El panel de este ejemplo se abre dentro de la página y deja disponible el resto del contenido. Tab puede continuar por el documento. Una capa que inutiliza el fondo requiere otro diseño de interacción.
¿48rem sirve siempre como punto de corte? ▼
Es el valor predeterminado de md en Tailwind. Prueba los textos traducidos más largos con la fuente definitiva y el texto ampliado. Retrasa el cambio si cuesta encontrar los enlaces o acceder a la cuenta.

Artículos relacionados