Flexbox: la tabla ancha rompe el layout (falta min-w-0)

Tienes un panel con barra lateral y un contenido principal, todo con Flexbox. Pones dentro una tabla ancha y, en lugar de aparecer una barra de desplazamiento horizontal en la tabla, ocurre esto:

  • Toda la página se ensancha y aparece un scroll horizontal en el navegador, no en la tabla.
  • Aparece además un segundo scroll vertical fuera del contenido: al bajar se mueven la barra lateral y la cabecera, y debajo queda una zona en blanco.
  • En un modal con altura limitada, las filas de una lista se aplastan hasta quedar de unos pocos píxeles.

Esta es una estructura típica que produce el problema:

<div class="flex h-screen">
  <aside class="hidden w-56 shrink-0 bg-slate-900 p-4 text-white md:block">Menú</aside>
  <div class="flex flex-1 flex-col">
    <header class="h-14 shrink-0 border-b px-4">Cabecera</header>
    <main class="flex-1 p-4">
      <div class="overflow-y-auto">
        <div class="overflow-x-auto">
          <table class="w-[2600px] table-fixed text-sm">...</table>
        </div>
      </div>
    </main>
  </div>
</div>

Al medirla en el navegador con una ventana de 1366 × 768 px, estos fueron los valores:

columna de contenido:   2632 px de ancho   (la tabla mide 2600 px)
página:                 scrollWidth 2856, clientWidth 1351
contenedor de la tabla: scrollWidth 2600 = clientWidth 2600  -> nunca hay barra

Los tres síntomas tienen una raíz común en cómo Flexbox calcula el tamaño mínimo de sus elementos.

Por qué pasa

Los elementos hijos de un contenedor flex tienen por defecto min-width: auto y min-height: auto. Según MDN, para un elemento flex ese mínimo automático es el tamaño mínimo de su contenido (min-content), salvo que el elemento sea un contenedor de scroll: en ese caso el mínimo automático es 0. La especificación (CSS Flexible Box Layout, sección "Automatic Minimum Size of Flex Items") lo expresa igual: el mínimo automático es el tamaño mínimo basado en el contenido y, para los contenedores de scroll, cero.

Esto explica los tres síntomas:

  1. La tabla ensancha todo. La columna de contenido (flex-1) no es un contenedor de scroll, así que no puede ser más estrecha que el contenido más ancho que lleve dentro: la tabla de 2600 px. El contenedor con overflow-x: auto que envuelve la tabla recibe un ancho de 2600 px, no desborda y por eso no muestra barra. Es la columna la que desborda a la página.
  2. El segundo scroll vertical. Si el <main> es un elemento flex en columna y el scroll lo tiene un elemento interior (o ninguno), su altura mínima automática es la del contenido: <main> crece y empuja todo. Con un contenedor de altura 100vh, el desbordamiento lo gestiona la página, y la barra lateral (que mide lo mismo que el contenedor) se desplaza con ella.
  3. Las filas aplastadas. flex-shrink vale 1 por defecto (MDN), así que los elementos se encogen para no desbordar el contenedor. Normalmente min-height: auto impide que bajen del tamaño de su contenido, pero una fila con overflow: hidden (por ejemplo, para recortar texto con truncate) es un contenedor de scroll, su mínimo automático es 0 y se encoge hasta quedarse en su relleno (padding). Con py-2 eso son 16 px.

Solución

  1. Estructura del layout con Tailwind. Este es el esqueleto completo (puedes ponerlo en el layout.tsx de tu panel en Next.js):

    <div class="flex h-dvh overflow-hidden">
      <aside class="hidden w-56 shrink-0 bg-slate-900 p-4 text-white md:block">Menú</aside>
    
      <div class="flex min-w-0 flex-1 flex-col">
        <header class="h-14 shrink-0 border-b px-4">Cabecera</header>
    
        <main class="min-h-0 flex-1 overflow-y-auto p-4">
          <div class="overflow-x-auto">
            <table class="w-[2600px] table-fixed text-sm">
              <!-- filas -->
            </table>
          </div>
        </main>
      </div>
    </div>
    • h-dvh overflow-hidden en el contenedor raíz: la página ocupa exactamente la ventana y nunca tiene scroll propio. h-dvh equivale a height: 100dvh (Tailwind lo documenta como la altura del viewport que cambia cuando se expande o se contrae la interfaz del navegador); si tu versión no lo incluye, usa h-screen.
    • min-w-0 en el hijo flex que contiene la tabla: devuelve el mínimo a 0 y deja que la columna se ajuste al espacio disponible.
    • min-h-0 flex-1 overflow-y-auto en <main>: es el único elemento con scroll vertical.
    • overflow-x-auto en el contenedor directo de la tabla: es el único elemento con scroll horizontal. Un contenedor de scroll por eje y por tabla; no repitas overflow en los ancestros.
    • shrink-0 en la barra lateral y la cabecera, para que no se encojan.
  2. El mismo layout en CSS puro:

    .shell   { display: flex; height: 100dvh; overflow: hidden; }
    .sidebar { width: 14rem; flex-shrink: 0; }
    .content { display: flex; flex-direction: column; flex: 1; min-width: 0; }
    .header  { flex-shrink: 0; height: 3.5rem; }
    .main    { flex: 1; min-height: 0; overflow-y: auto; padding: 1rem; }
    .table-scroll { overflow-x: auto; }
    .table-scroll table { width: 2600px; }

    En Tailwind 4.3, min-w-0 genera min-width: 0, min-h-0 genera min-height: 0, shrink-0 genera flex-shrink: 0 y h-dvh genera height: 100dvh.

  3. Listas con scroll dentro de un modal o un panel de altura limitada: evita que las filas se encojan.

    <div class="flex max-h-72 w-80 flex-col rounded border bg-white">
      <div class="shrink-0 border-b p-3">Buscar</div>
      <ul class="flex flex-col overflow-y-auto">
        <li class="shrink-0 overflow-hidden px-3 py-2">Resultado 1</li>
        <!-- más filas -->
      </ul>
    </div>

    En CSS: li { flex-shrink: 0; }. Así la lista conserva la altura de cada fila y desborda, y el scroll aparece en el <ul>.

Cómo verificar que quedó resuelto

En la consola del navegador, con la vista que falla abierta:

const s = document.scrollingElement;
({ paginaScrollX: s.scrollWidth > s.clientWidth, paginaScrollY: s.scrollHeight > s.clientHeight })

Ambos valores deben ser false. Después comprueba que el único scroll vertical está en <main> (main.scrollHeight > main.clientHeight) y que el contenedor de la tabla desborda por dentro (wrapper.scrollWidth > wrapper.clientWidth). Repite con el ancho de un móvil.

En la prueba, con ventanas de 1366 × 768 y de 375 × 812 px (Chromium), la versión que falla tenía scroll de página en ambos ejes y la columna medía 2632 px. Con el arreglo, la página no tenía scroll en ningún eje, <main> era el único con scroll vertical y la tabla de 2600 px se desplazaba dentro de su contenedor (1095 px visibles en escritorio, 328 px en móvil). En el modal, las filas pasaron de 16 px a su altura natural (entre 40 y 64 px, según si el texto ocupaba una o dos líneas).

Variantes y errores relacionados

overflow-hidden en el contenedor raíz sin min-w-0

Si pones overflow-hidden en el contenedor raíz pero olvidas min-w-0, el scroll de página desaparece y el problema queda oculto: en la prueba la columna seguía midiendo 2647 px, el contenedor de la tabla no mostraba barra y la tabla quedaba recortada por la ventana. Si ves una tabla cortada sin forma de desplazarla, revisa min-w-0.

¿Hace falta min-h-0 si ya hay overflow-y-auto?

Si <main> es el propio contenedor de scroll, no: su mínimo automático ya es 0 (en la prueba funcionó igual sin min-h-0). Sí hace falta cuando el scroll lo tiene un elemento interior, por ejemplo un <div class="h-full overflow-y-auto"> dentro de <main>: sin min-h-0, <main> creció hasta el alto de su contenido (2304 px); con min-h-0 se quedó en el espacio disponible. Dejar ambas clases es inocuo y evita depender de ese detalle.

Lo mismo en CSS Grid

MDN indica que el mínimo automático también afecta a los elementos de una rejilla. En la prueba, con grid-template-columns: 14rem 1fr la columna medía 2600 px y la página desbordaba; con 14rem minmax(0, 1fr) la columna medía 1142 px y la tabla se desplazaba dentro de su contenedor. En Tailwind: grid-cols-[14rem_minmax(0,1fr)].

Texto largo sin espacios en un elemento flex

Una URL o un identificador largo sin espacios puede ensanchar un elemento flex por el mismo motivo. En la prueba, con solo overflow-wrap: break-word (clase break-words en Tailwind) la columna siguió desbordando la página en un ancho de 375 px; al añadir min-w-0 a ese elemento el texto se partió y la página dejó de desbordar. Si prefieres cortar el texto con puntos suspensivos, usa truncate junto con min-w-0.

Resumen

  • Los elementos flex no se encogen por debajo del tamaño de su contenido (min-width: auto y min-height: auto), salvo que sean contenedores de scroll.
  • Una tabla ancha estira la columna de contenido; pon min-w-0 en el hijo flex que la contiene y overflow-x-auto en su envoltorio directo.
  • Evita el segundo scroll vertical con un contenedor raíz h-dvh overflow-hidden y min-h-0 flex-1 overflow-y-auto en <main>.
  • Las filas de una lista con scroll necesitan shrink-0 para no aplastarse cuando el contenedor tiene altura limitada.
  • Verifica con scrollWidth y clientWidth de la página: ambos ejes sin scroll de página y un solo contenedor de scroll por eje.

Referencias

Comentarios