Módulo 08: Flexbox

Objetivos del módulo

  • Entender el modelo de flexbox: contenedor, elementos, eje principal y eje transversal
  • Elegir entre flex e inline-flex y saber qué les cambia a los hijos directos
  • Controlar la dirección con flex-row y flex-col, y conocer el problema de accesibilidad de *-reverse
  • Partir una fila en varias con flex-wrap y separar con gap-* en lugar de space-*
  • Calcular a mano cómo reparten el espacio basis-*, grow-* y shrink-*
  • Usar con criterio los atajos flex-1, flex-auto, flex-initial y flex-none
  • Resolver el error más frecuente de flexbox: el texto que no se trunca, con min-w-0
  • Cambiar el orden visual con order-* sin romper el orden de lectura
  • Alinear con justify-*, items-*, self-* y content-*, y distinguir cuándo actúa cada uno
  • Saber por qué justify-items-* y justify-self-* no hacen nada en flexbox, y usar márgenes automáticos
  • Entender qué hacen realmente las clases place-* en un contenedor flex
  • Alinear por la última línea base con items-baseline-last y evitar la pérdida de datos con la alineación segura
  • Montar los patrones reales: cabecera, tarjetas con pie alineado, fila de datos, pie pegado al fondo y panel con barra lateral
  • Decidir cuándo usar flexbox y cuándo Grid

1. Preparación del laboratorio

Sigues con el proyecto Vite del módulo 01. Para que los ejemplos se lean bien, usan dos clases de decoración que no forman parte de Tailwind ni de la técnica que se enseña. Solo dibujan las cajas de colores y el marco discontinuo de las figuras, de modo que en cada ejemplo destaquen las clases que importan. Añádelas a app.css:

@import "tailwindcss";

@layer components {
  .caja {
    border-radius: 0.375rem;
    background-color: #6366f1;
    padding: 0.5rem 0.75rem;
    color: #fff;
    font-size: 0.875rem;
    font-weight: 500;
    text-align: center;
  }
  .marco {
    border: 2px dashed #cbd5e1;
    border-radius: 0.5rem;
    background-color: #f8fafc;
    padding: 0.25rem;
  }
}

Están en la capa components, así que cualquier utilidad las sobrescribe: por eso bg-orange-500 cambia el color de una .caja. Viste el motivo en las capas de Tailwind. El relleno de .caja importará más adelante, cuando hagamos cuentas con shrink.

Los rótulos en letra de máquina de las capturas son solo leyendas: no aparecen en el código de los ejemplos. Si necesitas repasar el flexbox de CSS puro, tienes el Curso de HTML y CSS.

⚠️ Importante: Vite no detecta siempre los HTML creados con el servidor en marcha. Si una clase nueva no aparece, guarda app.css sin cambiar nada para que Tailwind vuelva a escanear el proyecto.


2. Cómo piensa flexbox

Antes de flexbox, colocar elementos en fila exigía recurrir a float o a inline-block, técnicas que no se diseñaron para eso. Flexbox es un modo de colocación pensado para una sola tarea: repartir elementos a lo largo de una línea y decidir qué hacer con el espacio que sobra o que falta.

Todo gira en torno a dos papeles:

  • El contenedor flex es el elemento que lleva display: flex.
  • Los elementos flex son sus hijos directos. Los nietos no participan: siguen su flujo normal.

📘 Concepto: Flexbox es unidimensional. Trabaja con una línea cada vez, y aunque esa línea se parta en varias, cada una reparte su espacio por su cuenta. Si necesitas que filas y columnas se alineen entre sí, lo que buscas es CSS Grid.

Los dos ejes

Flexbox no habla de “horizontal” y “vertical”, sino de dos ejes que dependen de la dirección que elijas:

  • El eje principal es la dirección en la que se colocan los elementos.
  • El eje transversal es el perpendicular.

Dos contenedores flex: en flex-row el eje principal es horizontal y el transversal vertical; en flex-col es al revés

Figura 1. Al pasar a flex-col los ejes se intercambian, y con ellos lo que hace cada clase de alineación.

Esta idea evita la mitad de los errores de flexbox. justify-center no significa “centrar en horizontal”, sino “centrar en el eje principal”. En una columna, ese eje es vertical.

flex e inline-flex

Las dos clases convierten el elemento en contenedor flex. La diferencia es cómo se comporta el contenedor hacia fuera:

<!-- Bloque: ocupa toda la línea y parte el párrafo -->
<p>Estado del pedido:
  <span class="flex gap-2">
    <span class="caja">Pagado</span>
    <span class="caja">Enviado</span>
  </span>
  según el almacén.
</p>

<!-- En línea: convive con el texto, como una palabra -->
<p>Estado del pedido:
  <span class="inline-flex gap-2">
    <span class="caja">Pagado</span>
    <span class="caja">Enviado</span>
  </span>
  según el almacén.
</p>
.flex { display: flex; }
.inline-flex { display: inline-flex; }

Con flex el grupo de etiquetas rompe el párrafo en tres líneas; con inline-flex queda dentro de la frase

Figura 2. flex hacia fuera es un bloque; inline-flex hacia fuera es una palabra. Hacia dentro funcionan igual.

inline-flex es lo habitual en botones con icono y etiquetas de estado: elementos que deben medir lo que su contenido y convivir con el texto de alrededor.

Qué les pasa a los hijos

Al entrar en un contenedor flex, los hijos dejan de seguir las reglas del flujo normal:

Antes Dentro de un contenedor flex
Un <span> es en línea y no acepta width Se convierte en bloque y acepta width y height
float saca al elemento del flujo float se ignora
vertical-align coloca respecto a la línea de texto vertical-align se ignora
Los márgenes verticales pueden colapsar Los márgenes de los elementos flex nunca colapsan
Un texto suelto va en la línea Un texto suelto se envuelve en un elemento anónimo
<!-- Sin flex: w-40 no tiene efecto sobre un span en línea -->
<div class="marco">
  <span class="caja w-40">Ana</span>
  <span class="caja w-40">Luis</span>
</div>

<!-- Con flex: los mismos span aceptan el ancho -->
<div class="marco flex gap-2">
  <span class="caja w-40">Ana</span>
  <span class="caja w-40">Luis</span>
</div>

Los span sin flex miden lo que su texto; con flex miden 160 píxeles

Figura 3. Mismos <span>, mismas clases. Lo único que cambia es el padre.

⚠️ Importante: La última fila de la tabla da una sorpresa frecuente. En <div class="flex">Hola <b>Ana</b>, bienvenida</div> hay tres elementos flex: “Hola”, <b> y “, bienvenida”. Los espacios entre ellos desaparecen y gap-* los separa como si fueran cajas. Si quieres que un texto con formato fluya normalmente, envuélvelo en un único <p> o <span>.


3. Dirección

El valor inicial es flex-row, así que class="flex" ya coloca en fila y casi nunca hace falta escribirlo. Solo se escribe para volver a la fila desde otro punto de ruptura, como flex-col md:flex-row.

Clase CSS Eje principal
flex-row flex-direction: row En el sentido del texto
flex-row-reverse flex-direction: row-reverse En sentido contrario al texto
flex-col flex-direction: column De arriba abajo
flex-col-reverse flex-direction: column-reverse De abajo arriba
<div class="marco flex flex-row gap-2">...</div>
<div class="marco flex flex-row-reverse gap-2">...</div>
<div class="marco flex flex-col gap-2">...</div>
<div class="marco flex flex-col-reverse gap-2">...</div>

Las cuatro direcciones con tres cajas numeradas

Figura 4. En las columnas, las cajas ocupan todo el ancho: el valor por defecto stretch actúa ahora en el eje horizontal.

Fíjate en dos detalles de la figura. flex-row-reverse no solo invierte el orden: también lleva los elementos al lado opuesto del contenedor, porque el inicio del eje se ha movido allí. Y en flex-col las cajas se estiran a lo ancho. Es el mismo estiramiento que en una fila iguala las alturas: si quieres que una columna deje a sus hijos con su ancho natural, añade items-start.

*-reverse y el orden de lectura

flex-row-reverse cambia lo que se ve, pero no el HTML. Para probarlo, se pueden poner tres enlaces en un contenedor flex-row-reverse, que se ven como “Tercero, Segundo, Primero”, y recorrerlos con el tabulador:

<div class="flex flex-row-reverse gap-2">
  <a href="#" class="caja">Primero</a>
  <a href="#" class="caja">Segundo</a>
  <a href="#" class="caja">Tercero</a>
</div>
Foco con el tabulador: Primero > Segundo > Tercero
Posición en pantalla:  el foco salta de derecha a izquierda

El foco sigue el orden del documento, y un lector de pantalla también. Quien usa teclado ve saltar el foco en sentido contrario al que está leyendo.

⚠️ Importante: Usa *-reverse y order-* solo para cambios que no afecten al sentido del contenido. Si el orden importa para entender la página, cambia el orden en el HTML.

Dirección del texto: LTR y RTL

flex-row no significa “de izquierda a derecha”, sino “en el sentido del texto”. Con dir="rtl", como en árabe o hebreo, los elementos se colocan de derecha a izquierda sin tocar ninguna clase. Lo mismo vale para justify-start: “inicio” es la derecha en ese caso.

Para que el resto del diseño acompañe, usa las propiedades lógicas que viste en el módulo 03: ms-auto en lugar de ml-auto.

<div dir="ltr" class="marco flex gap-2">
  <div class="caja">1</div>
  <div class="caja">2</div>
  <div class="caja ms-auto bg-orange-500">ms-auto</div>
</div>

<div dir="rtl" class="marco flex gap-2">
  <div class="caja">1</div>
  <div class="caja">2</div>
  <div class="caja ms-auto bg-orange-500">ms-auto</div>
</div>

<div dir="rtl" class="marco flex gap-2">
  <div class="caja">1</div>
  <div class="caja">2</div>
  <div class="caja ml-auto bg-gray-500">ml-auto</div>
</div>

Tres filas: con ms-auto la caja se va al extremo correcto en ambas direcciones; con ml-auto en RTL queda pegada a las demás

Figura 5. ms-auto se adapta a la dirección del texto. ml-auto empuja siempre desde la izquierda física y en RTL no separa nada.

La clase ms-auto se explica en la sección 7, con los márgenes automáticos.


4. Varias líneas y separación

flex-wrap

Por defecto un contenedor flex mete todos los elementos en una sola línea, aunque tenga que encogerlos. flex-wrap les permite pasar a la línea siguiente.

Clase CSS Efecto
flex-nowrap flex-wrap: nowrap Una sola línea (valor inicial)
flex-wrap flex-wrap: wrap Salta de línea cuando no cabe
flex-wrap-reverse flex-wrap: wrap-reverse Salta de línea, y las líneas se apilan al revés
<div class="marco flex flex-nowrap gap-2">
  <div class="caja w-24">1</div>
  <!-- ...hasta 6 -->
</div>
<div class="marco flex flex-wrap gap-2">...</div>
<div class="marco flex flex-wrap-reverse gap-2">...</div>

Seis cajas de 96 píxeles en un contenedor estrecho, con las tres opciones de wrap

Figura 6. Con flex-nowrap las cajas miden menos de lo que pide w-24; con flex-wrap conservan sus 96 px.

Mira la primera fila: seis cajas con w-24 deberían medir 96 px cada una, pero son más estrechas. w-24 es un punto de partida, no una orden: como los elementos flex pueden encoger, lo hacen hasta caber. Lo explica la sección 5.

Si impides que encojan con shrink-0, la fila se sale del contenedor:

<div class="marco flex gap-2">
  <div class="caja w-24 shrink-0">1</div>
  <!-- ...hasta 6 -->
</div>

Las cajas sobrepasan el borde del contenedor y dejan de verse

Figura 7. Un elemento que no puede encoger ni saltar de línea no tiene más salida que desbordarse.

Este desbordamiento suele ser un error, salvo que lo busques a propósito: una fila con desplazamiento horizontal se monta así, con overflow-x-auto en el contenedor. Lo verás en la alineación segura, en la sección 7.

gap-*

gap deja un hueco solo entre elementos: nunca antes del primero ni después del último, y también funciona entre líneas.

<div class="marco flex flex-wrap gap-4">...</div>
<div class="marco flex flex-wrap gap-x-8 gap-y-1">...</div>
.gap-4 { gap: calc(var(--spacing) * 4); }
.gap-x-8 { column-gap: calc(var(--spacing) * 8); }
.gap-y-1 { row-gap: var(--spacing); }

Dos contenedores: gap-4 con el mismo hueco en ambos sentidos; gap-x-8 gap-y-1 con huecos distintos

Figura 8. gap-x-* separa dentro de la línea y gap-y-* separa las líneas.

gap frente a space-* con varias líneas 🔄 v4.0

🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) space-x-* pone el margen a la izquierda de cada hijo menos el primero, así que con flex-wrap la segunda fila se desplaza. Si usas la LTS: usa gap-* en cualquier contenedor flex, que se comporta igual en las dos versiones.

Las clases space-* que viste en el módulo 03 no son flexbox: ponen márgenes a los hijos. Por eso no conocen las líneas de un contenedor flex.

<div class="marco flex flex-wrap space-x-4">...</div>
<div class="marco flex flex-wrap gap-4">...</div>

Con space-x-4 las dos filas quedan pegadas; con gap-4 están separadas

Figura 9. space-x-4 no separa las filas. Con gap-4 no hace falta nada más.

Medido con seis cajas de 96 px en un contenedor de 400 px:

Versión Clases Posición del 4 (primera de la segunda fila) Separación entre filas
4.3.3 space-x-4 0 px 0 px
3.4.19 space-x-4 16 px (descuadrada) 0 px
Ambas gap-4 0 px 16 px

💡 Consejo: En un contenedor flex o Grid usa siempre gap-*. Reserva space-* para pilas de bloques normales, sin flex.


5. El tamaño de los elementos: basis, grow y shrink

Esta sección es el corazón de flexbox. Si la entiendes, dejarás de probar clases al azar hasta que algo funcione.

Cada elemento flex tiene tres valores:

Propiedad Pregunta que responde Valor inicial Clases
flex-basis ¿Qué tamaño pide de partida? auto basis-*
flex-grow Si sobra sitio, ¿qué parte se lleva? 0 grow, grow-*
flex-shrink Si falta sitio, ¿qué parte cede? 1 shrink, shrink-*

Los valores iniciales explican el comportamiento de partida: los elementos miden lo que pide su contenido (auto), no crecen (0) y sí encogen (1). Por eso la fila de seis cajas de la figura 6 encogía, y por eso unos elementos en fila no ocupan solos todo el ancho.

basis-*

flex-basis es el tamaño de partida en el eje principal: el ancho en una fila, el alto en una columna.

<div class="marco flex gap-2">
  <div class="caja basis-32">basis-32</div>
  <div class="caja basis-48">basis-48</div>
  <div class="caja basis-64">basis-64</div>
</div>

<div class="marco flex">
  <div class="caja basis-1/4">1/4</div>
  <div class="caja basis-1/2 bg-orange-500">1/2</div>
  <div class="caja basis-1/4">1/4</div>
</div>

<div class="marco flex">
  <div class="caja w-32 basis-64">w-32 basis-64</div>
</div>
.basis-32 { flex-basis: calc(var(--spacing) * 32); }
.basis-1\/4 { flex-basis: calc(1 / 4 * 100%); }
.basis-full { flex-basis: 100%; }
.basis-auto { flex-basis: auto; }

Tres filas: anchos con la escala de espaciado, fracciones y basis ganando a width

Figura 10. En la última fila basis-64 gana a w-32: la caja mide 256 px.

La regla es que flex-basis gana a width si los dos están definidos. Con basis-auto, que es el valor inicial, el navegador usa width, y si tampoco hay width, el contenido.

📘 Concepto: ¿w-* o basis-*? En una fila dan casi el mismo resultado. basis-* tiene dos ventajas: sigue el eje, así que en un flex-col md:flex-row significa alto en móvil y ancho en escritorio; y deja claro que hablas de un reparto flex. w-* es más corto y se entiende fuera de flexbox. Ninguno es un tamaño fijo: los dos son el punto de partida del reparto.

grow-* 🔄 v4.0

🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) solo existen grow y grow-0, y grow-2 o grow-3 no generan CSS. Si usas la LTS: escribe el valor entre corchetes, grow-[2] y grow-[3]. Medido con el ejemplo de esta sección, el reparto es idéntico.

Si después de colocar los elementos con su basis sobra espacio, flex-grow decide cómo se reparte.

<!-- Nadie crece: el sobrante queda a la derecha -->
<div class="marco flex gap-2">
  <div class="caja">Uno</div>
  <div class="caja">Dos</div>
  <div class="caja">Tres</div>
</div>

<!-- Solo el segundo: se lo lleva todo -->
<div class="marco flex gap-2">
  <div class="caja">Uno</div>
  <div class="caja grow bg-orange-500">Dos</div>
  <div class="caja">Tres</div>
</div>

<!-- Todos: lo reparten a partes iguales -->
<div class="marco flex gap-2">
  <div class="caja grow">Uno</div>
  <div class="caja grow">Dos</div>
  <div class="caja grow">Tres</div>
</div>

Tres filas: sin grow, con grow en el segundo y con grow en todos

Figura 11. grow equivale a flex-grow: 1. Lo que importa es la proporción entre elementos, no el número.

El reparto hecho a mano

Con valores distintos de grow, el cálculo es siempre el mismo. Tres elementos con basis-[100px] en un contenedor de 600 px y grow, grow-2 y grow-3:

<div class="flex" style="width: 600px">
  <div class="caja grow basis-[100px]">A</div>
  <div class="caja grow-2 basis-[100px]">B</div>
  <div class="caja grow-3 basis-[100px] bg-orange-500">C</div>
</div>

Diagrama en tres pasos: se colocan las bases, se divide el espacio libre entre seis y se suma a cada elemento su parte

Figura 12. grow reparte el espacio libre, no el total.

Las tres cajas medidas: A 150, B 200, C 250

Figura 13. El resultado real en el navegador: 150, 200 y 250 px, igual que en el cálculo.

⚠️ Importante: grow-3 no hace que el elemento mida el triple que uno con grow. Recibe el triple del espacio libre. C mide 250 px, no 450. Para obtener tamaños proporcionales, la base debe ser cero, y eso es lo que hace flex-1 en la sección de atajos.

shrink-* 🔄 v4.0

🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) solo existen shrink y shrink-0, y shrink-2 no genera CSS. Si usas la LTS: escribe el valor entre corchetes, shrink-[2]. Medido con el ejemplo de esta sección, el reparto es idéntico.

flex-shrink es el caso contrario: si los elementos no caben, decide quién cede. Con shrink hay una diferencia importante respecto a grow: lo que se reparte se pondera por el tamaño base. Tiene sentido: quitar 50 px a una caja de 60 es mucho más grave que quitárselos a una de 600.

La fórmula es:

exceso = suma de las bases - contenedor
peso de cada elemento = shrink × tamaño base
reducción = exceso × peso / suma de los pesos

Probémoslo en un contenedor de 500 px, con un exceso de 100 px:

<!-- 1. Dos de 300 px con el shrink por defecto -->
<div class="flex" style="width: 500px">
  <div class="caja basis-[300px]">A</div>
  <div class="caja basis-[300px]">B</div>
</div>

<!-- 2. B cede el doble -->
<div class="flex" style="width: 500px">
  <div class="caja basis-[300px]">A</div>
  <div class="caja shrink-2 basis-[300px] bg-orange-500">B</div>
</div>

<!-- 3. A no cede nada -->
<div class="flex" style="width: 500px">
  <div class="caja shrink-0 basis-[300px] bg-orange-500">A</div>
  <div class="caja basis-[300px]">B</div>
</div>

<!-- 4. Bases distintas, mismo shrink -->
<div class="flex" style="width: 500px">
  <div class="caja basis-[400px]">A</div>
  <div class="caja basis-[200px]">B</div>
</div>

Cuatro filas con los resultados de cada combinación de shrink

Figura 14. Con shrink-0, A conserva sus 300 px y B asume todo el exceso.

Caso Bases shrink A mide B mide
1 300 y 300 1 y 1 250 px 250 px
2 300 y 300 1 y 2 266,67 px 233,33 px
3 300 y 300 0 y 1 300 px 200 px
4 400 y 200 1 y 1 331,89 px 168,11 px

El caso 4 merece una parada. Con la fórmula, los pesos serían 400 y 200, y A cedería dos tercios de 100 px: mediría 333,33 px. Pero el navegador da 331,89 px. El motivo es que el peso se calcula sobre el tamaño base sin relleno ni borde. .caja tiene 12 px de relleno a cada lado, y Preflight aplica box-sizing: border-box, así que las bases internas son 376 y 176 px:

pesos: 376 y 176, suma 552
A cede 100 × 376 / 552 = 68,12 px   →   400 - 68,12 = 331,88 px
B cede 100 × 176 / 552 = 31,88 px   →   200 - 31,88 = 168,12 px

Ahora sí coincide con la medida, salvando el redondeo interno del navegador. En la práctica no harás estas cuentas casi nunca, pero así sabes por qué dos cálculos “correctos” no cuadran al píxel.

El uso diario de shrink se reduce a una clase, shrink-0, para lo que nunca debe encogerse:

  • Avatares e iconos: si encogen, se deforman.
  • Precios, fechas y botones junto a un texto largo.
  • Cualquier elemento en una fila con desplazamiento horizontal.

Los atajos flex-*

La propiedad flex fija los tres valores a la vez, y Tailwind ofrece cuatro combinaciones habituales.

Clase CSS en 4.3 Equivale a (grow shrink basis) Para qué
flex-1 flex: 1 1 1 0% Reparto proporcional que ignora el contenido
flex-auto flex: auto 1 1 auto Crece según su contenido
flex-initial flex: 0 auto 0 1 auto No crece, sí encoge (comportamiento por defecto)
flex-none flex: none 0 0 auto Rígido: ni crece ni encoge

El mismo par de elementos, uno con texto corto y otro con texto largo:

<div class="marco flex gap-2">
  <div class="caja flex-1">Sí</div>
  <div class="caja flex-1 bg-orange-500">Un texto bastante más largo</div>
</div>
<!-- Igual con flex-auto, flex-initial y flex-none -->

Cuatro filas: flex-1 da mitades iguales, flex-auto reparte según el contenido y las otras dos miden lo justo

Figura 15. Con flex-1 el texto largo no influye: base cero, partes iguales.

Las medidas reales, con 580 px disponibles para las dos cajas:

Clase “Sí” Texto largo Explicación
flex-1 290 px 290 px Las dos parten de 0 y reciben la mitad
flex-auto 206 px 374 px Parten de su contenido y reparten el sobrante a partes iguales
flex-initial 35 px 203 px Miden su contenido, no crecen
flex-none 35 px 203 px Igual aquí, porque hay sitio. Se diferencian si falta espacio: flex-none no cede

La pregunta de examen es la diferencia entre flex-1 y grow. Los dos crecen, pero grow parte del tamaño del contenido y flex-1 parte de cero:

  • Usa flex-1 para columnas que deben ser iguales, sea cual sea su contenido.
  • Usa grow para el elemento que debe llenar el hueco que dejan los demás.

Valores dinámicos en flex-* 🆕 v4.0

🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: escribe el valor entre corchetes: flex-[2] en lugar de flex-2 y flex-[50%] en lugar de flex-1/2. El resultado medido es idéntico.

En v3, el único flex-* numérico era flex-1. En v4 acepta cualquier número y también fracciones:

<div class="flex gap-2">
  <div class="caja flex-1">flex-1</div>
  <div class="caja flex-2 bg-orange-500">flex-2</div>
  <div class="caja flex-1">flex-1</div>
</div>

<div class="flex">
  <div class="caja flex-1/2">flex-1/2</div>
  <div class="caja flex-1/4">flex-1/4</div>
  <div class="caja flex-1/4">flex-1/4</div>
</div>
.flex-2 { flex: 2; }
.flex-1\/2 { flex: calc(1/2 * 100%); }

Dos filas: proporciones uno, dos, uno con flex-1 y flex-2, y mitades y cuartos con fracciones

Figura 16. Con base cero, flex-2 se lleva el doble de espacio que flex-1.

Las medidas reales en 600 px, con dos huecos de 8 px, son 152, 280 y 152 px. No es exactamente 1:2:1, y otra vez el responsable es el relleno de .caja: un elemento no puede ser más estrecho que su relleno, así que cada caja parte de 24 px aunque su base sea cero.

Disponible: 600 - 16 de huecos = 584 px
Relleno de las tres cajas: 3 × 24 = 72 px
Espacio libre: 584 - 72 = 512 px
Unidades: 1 + 2 + 1 = 4, a 128 px cada una

flex-1: 24 + 128 = 152 px
flex-2: 24 + 256 = 280 px

Si necesitas proporciones exactas, deja el elemento flex sin relleno y pónselo a un hijo interno.

⚠️ Importante: flex-1/2 no es basis-1/2. Compila a flex: 50%, que significa 1 1 50%: el elemento también crece y encoge. basis-1/2 solo fija la base y mantiene grow a 0.

Anchos con nombre y variables en basis 🆕 v4.0

🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: para basis-xs escribe basis-[20rem]; para basis-17, basis-[4.25rem]; para un nombre propio, añádelo en theme.extend.flexBasis de tailwind.config.js; y para una variable, basis-[var(--ancho)].

basis-* admite en v4 la escala de anchos con nombre (basis-xs, basis-md…) y cualquier múltiplo del espaciado (basis-17). Además, si defines tu propio ancho en el tema, la clase aparece sola:

@theme {
  --container-tarjeta: 18rem;
}
<div class="flex gap-2">
  <div class="caja basis-tarjeta">basis-tarjeta</div>
  <div class="caja basis-17">17</div>
  <div class="caja basis-xs">basis-xs</div>
</div>
.basis-tarjeta { flex-basis: var(--container-tarjeta); }
.basis-17 { flex-basis: calc(var(--spacing) * 17); }
.basis-xs { flex-basis: var(--container-xs); }

Con espacio suficiente, las cajas miden 288, 68 y 320 px. Para una variable CSS suelta se usan paréntesis: basis-(--ancho), grow-(--peso), shrink-(--peso), order-(--pos) y flex-(--reparto).

El error más frecuente: min-w-0

Tienes una fila con un avatar y un texto largo, quieres puntos suspensivos, le pones truncate al texto… y no funciona.

<!-- Sin min-w-0: el div no puede ser más estrecho que su contenido -->
<div class="marco flex items-center gap-3">
  <div class="caja shrink-0">AG</div>
  <div>
    <p class="truncate text-sm font-medium">Ana García López, directora general de EMPRESA</p>
    <p class="truncate text-xs text-gray-500">ana.garcia@empresa.es</p>
  </div>
</div>

<!-- Con min-w-0 -->
<div class="marco flex items-center gap-3">
  <div class="caja shrink-0">AG</div>
  <div class="min-w-0">
    <p class="truncate text-sm font-medium">Ana García López, directora general de EMPRESA</p>
    <p class="truncate text-xs text-gray-500">ana.garcia@empresa.es</p>
  </div>
</div>

Arriba el texto se sale del contenedor; abajo termina en puntos suspensivos dentro del marco

Figura 17. Una sola clase en el padre del texto cambia el resultado: de 310 a 293 px, el ancho que cabe.

La causa es una regla poco conocida de flexbox. Los elementos flex tienen por defecto min-width: auto, que significa: “no encojas por debajo del ancho mínimo de tu contenido”. Para un texto con white-space: nowrap, como el de truncate, ese mínimo es la línea entera. Así que el div no encoge, y sus hijos con truncate nunca llegan a quedarse sin sitio.

min-w-0 retira esa protección y el div puede encoger hasta caber.

Dos matices que evitan añadir clases de más:

  • min-w-0 va en el elemento flex, que es el hijo directo del contenedor. En el ejemplo, el div, no los <p>.
  • Si el propio elemento flex es el que lleva truncate, no hace falta min-w-0. La regla min-width: auto no se aplica a los elementos cuyo overflow no es visible, y truncate incluye overflow: hidden. Por eso <p class="flex-1 truncate"> funciona sin más.

El mismo problema sucede con elementos que tienen un ancho mínimo propio, como <input> y <pre>, o con tablas anchas dentro de un elemento flex: si desbordan, prueba min-w-0.

Y existe la versión vertical, con min-h-0. Aparece con columnas flex anidadas cuando quieres que una zona interior tenga su propia barra de desplazamiento:

<div class="flex h-40 flex-col">
  <header class="h-10 shrink-0">Pedidos</header>
  <!-- Sin min-h-0 esta columna crece con su contenido y se sale de h-40 -->
  <div class="flex min-h-0 flex-1 flex-col">
    <ul class="flex-1 overflow-y-auto">...</ul>
  </div>
</div>

La <ul> no necesita min-h-0 porque ya tiene overflow-y-auto, pero su padre sí, porque es otro elemento flex y su mínimo automático es el alto de todo el contenido.

w-1/3 y basis-1/3 no cuentan el gap

Otra sorpresa habitual: tres columnas de un tercio con hueco entre ellas.

<div class="marco flex flex-wrap gap-4">
  <div class="caja basis-1/3">1</div>
  <div class="caja basis-1/3">2</div>
  <div class="caja basis-1/3">3</div>
</div>

<div class="marco flex gap-4">
  <div class="caja flex-1">1</div>
  <div class="caja flex-1">2</div>
  <div class="caja flex-1">3</div>
</div>

Con basis-1/3 la tercera caja salta de línea; con flex-1 las tres caben

Figura 18. Tres tercios suman el 100 % y los dos huecos, 32 px más. No caben.

Los porcentajes se calculan sobre el contenedor y el hueco se suma después. Las soluciones, de mejor a peor:

  1. flex-1 si son columnas iguales en una sola línea.
  2. CSS Grid con grid-cols-3 gap-4, que resta los huecos.
  3. Calcularlo a mano, si no hay otra opción: basis-[calc((100%-2rem)/3)].

6. Orden 🔄 v4.0

🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) order-* llega hasta order-12. Si usas la LTS: para valores mayores escribe order-[13]. El resto de clases de esta sección funciona igual.

order-* cambia la posición visual de un elemento sin tocar el HTML. Todos los elementos parten de order: 0, y se colocan de menor a mayor; a igualdad de valor, gana el orden del documento.

Clase CSS
order-1, order-2… order: 1, order: 2…
order-first order: -9999
order-last order: 9999
order-none order: 0
-order-1 order: calc(1 * -1)
<div class="marco flex gap-2">
  <div class="caja order-last bg-gray-500">Uno</div>
  <div class="caja">Dos</div>
  <div class="caja order-first bg-orange-500">Tres</div>
  <div class="caja">Cuatro</div>
</div>

<div class="marco flex gap-2">
  <div class="caja order-3">Uno</div>
  <div class="caja order-1">Dos</div>
  <div class="caja order-2">Tres</div>
  <div class="caja order-13">Cuatro</div>
</div>

Tres filas: sin order, con order-first y order-last, y con números explícitos

Figura 19. Los elementos se colocan de menor a mayor order, sea cual sea su posición en el HTML.

Aplican las mismas advertencias que a *-reverse: el tabulador y los lectores de pantalla siguen el orden del HTML. El uso correcto es el de un ajuste de presentación en un punto de ruptura, como mover una foto debajo del título en móvil. Tienes un caso real en el ejercicio 7.

💡 Consejo: Si escribes order-* en más de un elemento de la misma fila, probablemente el HTML esté en el orden equivocado. Corrígelo ahí.


7. Alineación

Hay cuatro familias de clases, y la confusión entre ellas es la fuente más habitual de frustración. Antes de ver los valores, fija qué mueve cada una.

Mapa de cuatro paneles: justify reparte por el eje principal, items alinea dentro de la línea, self alinea un elemento y content reparte las líneas

Figura 20. Tres clases se ponen en el contenedor y una, self-*, en el elemento.

Familia Se pone en Propiedad Actúa sobre
justify-* Contenedor justify-content Sobrante del eje principal
items-* Contenedor align-items Cada elemento dentro de su línea, en el eje transversal
self-* Elemento align-self Un elemento dentro de su línea, en el eje transversal
content-* Contenedor align-content Las líneas entre sí, en el eje transversal

justify-*: el eje principal

justify-content reparte el espacio que sobra. Si un elemento tiene grow o flex-1, no sobra nada y justify-* no tendrá ningún efecto visible.

Clase CSS Efecto
justify-start justify-content: flex-start Al principio
justify-center justify-content: center En el centro
justify-end justify-content: flex-end Al final
justify-between justify-content: space-between Extremos pegados, huecos iguales entre ellos
justify-around justify-content: space-around Cada elemento con el mismo hueco a cada lado
justify-evenly justify-content: space-evenly Todos los huecos iguales, incluidos los extremos
<div class="marco flex justify-between">
  <div class="caja w-16">1</div>
  <div class="caja w-16">2</div>
  <div class="caja w-16">3</div>
</div>

Seis filas, una por cada valor de justify

Figura 21. En around, el hueco de los extremos es la mitad que el central, porque entre dos elementos se suman sus dos márgenes. evenly los iguala.

La elección habitual: justify-between para los dos extremos de una barra o un pie de tarjeta, y justify-center para centrar.

justify-start, justify-center, etc., funcionan igual en las dos versiones. Las clases justify-normal y justify-stretch también existen, pero en un contenedor flex no aportan nada: se comportan como justify-start. justify-stretch solo tiene efecto en Grid.

justify-baseline: una clase que no hace nada 🆕 v4.0

🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: no pierdes nada, porque no tiene efecto. Para alinear por línea base, usa items-baseline.

Tailwind la genera (justify-content: baseline), pero baseline no es un valor válido de esa propiedad. En Chromium, CSS.supports('justify-content', 'baseline') devuelve false y la declaración se descarta. Si quieres alinear textos, usa items-baseline.

items-*: el eje transversal

align-items coloca cada elemento dentro de su línea en el eje transversal. Para que se note, hace falta que haya sitio: elementos de distinta altura o un contenedor con altura.

Clase CSS Efecto
items-stretch align-items: stretch Se estiran hasta llenar la línea (en la práctica, el valor por defecto)
items-start align-items: flex-start Arriba
items-center align-items: center Centrados
items-end align-items: flex-end Abajo
items-baseline align-items: baseline Sobre la misma línea base del texto
<div class="marco flex h-28 items-center gap-2">
  <div class="caja py-1">Bajo</div>
  <div class="caja py-6">Alto</div>
  <div class="caja py-3">Medio</div>
</div>

Cuatro contenedores con tres cajas de distinta altura y los valores stretch, start, center y end

Figura 22. Con items-stretch, las tres cajas miden lo mismo pese a tener distinto relleno.

El estiramiento por defecto es lo que hace que una fila de tarjetas salga con la misma altura sin hacer nada. También es lo que deforma una imagen colocada en una fila junto a un texto largo. Si te pasa, añade items-start al contenedor o self-start a la imagen.

⚠️ Importante: El estiramiento solo funciona si el elemento no tiene una altura propia. Un h-10 en el elemento gana a items-stretch, y es la causa más común de que “stretch no funcione”.

items-baseline

Cuando en una fila conviven tamaños de letra distintos, centrar no basta: las letras no se apoyan en la misma línea. La línea base es la línea imaginaria sobre la que descansan los textos.

<div class="marco flex items-center gap-4">
  <span class="text-4xl font-bold">899 €</span>
  <span class="text-sm text-gray-500">IVA incluido</span>
  <span class="caja pt-6">Comprar</span>
</div>

<div class="marco flex items-baseline gap-4">
  <span class="text-4xl font-bold">899 €</span>
  <span class="text-sm text-gray-500">IVA incluido</span>
  <span class="caja pt-6">Comprar</span>
</div>

Con items-baseline, el precio, la nota y el botón apoyan su texto en la misma línea

Figura 23. Abajo, “899 €”, “IVA incluido” y “Comprar” descansan sobre la misma línea. El botón se ha movido para lograrlo.

items-baseline-last y self-baseline-last 🆕 v4.1

🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: usa una propiedad arbitraria: [align-items:last_baseline] en el contenedor o [align-self:last_baseline] en el elemento. Comprobado que compila y da la misma posición. No sirve self-[last_baseline], que no genera CSS.

items-baseline alinea la primera línea de cada elemento. A veces necesitas lo contrario: que tarjetas de distinto alto cuadren por su última línea, para alinear el precio.

<div class="flex items-baseline-last gap-3">
  <article class="w-44 rounded-lg border border-gray-200 p-3">
    <h3 class="text-sm font-bold">Monitor 27 4K</h3>
    <p class="text-xs text-gray-600">Monitor IPS de 27 pulgadas con resolución 4K y puertos USB-C</p>
    <p class="mt-1 text-lg font-bold">329 €</p>
  </article>
  <article class="w-44 rounded-lg border border-gray-200 p-3">
    <h3 class="text-sm font-bold">Ratón inalámbrico</h3>
    <p class="text-xs text-gray-600">Ratón óptico</p>
    <p class="mt-1 text-lg font-bold">19,90 €</p>
  </article>
</div>
.items-baseline-last { align-items: last baseline; }
.self-baseline-last { align-self: last baseline; }

Arriba las tarjetas alinean la primera línea; abajo alinean los precios

Figura 24. Abajo, los precios quedan en la misma línea y la tarjeta baja se desplaza 32 px.

La compatibilidad no da problemas: según los datos de MDN (browser-compat-data), last baseline en align-items llegó a Chrome 108, Firefox 52 y Safari 16.2, todos por debajo del mínimo de Tailwind 4.

self-*: un solo elemento

self-* es items-* para un único elemento: se pone en el hijo y sustituye lo que diga el contenedor.

Clase CSS
self-auto align-self: auto (hereda lo que diga items-*)
self-start align-self: flex-start
self-center align-self: center
self-end align-self: flex-end
self-stretch align-self: stretch
self-baseline align-self: baseline
<div class="marco flex h-32 items-center gap-2">
  <div class="caja">sin self</div>
  <div class="caja self-start bg-orange-500">self-start</div>
  <div class="caja self-end bg-orange-500">self-end</div>
  <div class="caja self-stretch bg-orange-500">self-stretch</div>
  <div class="caja self-auto">self-auto</div>
</div>

Cinco cajas: dos centradas por el contenedor y tres que se salen con self

Figura 25. self-auto no es lo mismo que “sin alinear”: obedece al contenedor. Sirve para deshacer un self-* en otro punto de ruptura.

content-*: las líneas entre sí

align-content no mueve elementos: mueve líneas. Reparte el espacio transversal que sobra entre las líneas de un contenedor con flex-wrap.

Clase CSS
content-normal align-content: normal
content-stretch align-content: stretch
content-start align-content: flex-start
content-center align-content: center
content-end align-content: flex-end
content-between align-content: space-between
content-around align-content: space-around
content-evenly align-content: space-evenly
<div class="marco flex h-48 flex-wrap content-center gap-1">
  <div class="caja w-16">1</div>
  <!-- ...hasta 8 -->
</div>

Seis contenedores con ocho cajas en tres líneas y distintos valores de content

Figura 26. En content-stretch, las líneas crecen para llenar el contenedor y las cajas se estiran con ellas.

Dos cosas que debes tener presentes:

  • Sin flex-wrap, content-* no hace nada, aunque solo haya una línea. Medido en un contenedor de 96 px de alto: con content-end sin wrap, la caja sigue arriba (y = 6 px); al añadir flex-wrap, baja al fondo (y = 53 px), aunque siga habiendo una sola línea.
  • Para centrar un bloque de líneas se usa content-center, no items-center. Es la confusión clásica:
<div class="marco flex h-48 flex-wrap items-center gap-1">...</div>
<div class="marco flex h-48 flex-wrap content-center gap-1">...</div>

A la izquierda las dos líneas quedan muy separadas; a la derecha, juntas en el centro

Figura 27. Con items-center, cada línea ocupa la mitad del alto (por el stretch de las líneas) y las cajas se centran dentro de su mitad. Con content-center, las líneas se juntan en el centro.

Márgenes automáticos

Ninguna clase justify-* mueve un solo elemento. Para eso están los márgenes auto, que en flexbox tienen un superpoder: absorben todo el espacio libre de ese lado, antes de que actúe justify-*.

<!-- Empuja Salir al extremo derecho -->
<div class="marco flex gap-2">
  <div class="caja">Inicio</div>
  <div class="caja">Catálogo</div>
  <div class="caja ml-auto bg-orange-500">Salir</div>
</div>

<!-- Separa dos grupos -->
<div class="marco flex gap-2">
  <div class="caja">Inicio</div>
  <div class="caja ml-auto bg-orange-500">Pedidos</div>
  <div class="caja">Salir</div>
</div>

<!-- Centra uno entre dos -->
<div class="marco flex gap-2">
  <div class="caja">Izq.</div>
  <div class="caja mx-auto bg-orange-500">Centro</div>
  <div class="caja">Der.</div>
</div>

<!-- En columna: empuja hacia abajo -->
<div class="marco flex h-28 flex-col gap-2">
  <div class="caja">Arriba</div>
  <div class="caja mt-auto bg-orange-500">Abajo</div>
</div>

<!-- Centrado en los dos ejes -->
<div class="marco flex h-24">
  <div class="caja m-auto bg-orange-500">m-auto</div>
</div>

Cinco ejemplos de márgenes automáticos

Figura 28. El truco de ml-auto y mt-auto aparece en casi cualquier interfaz.

Para interfaces que puedan mostrarse en árabe o hebreo, recuerda usar ms-auto y me-auto (figura 5).

justify-items-* y justify-self-*: no en flexbox

Tailwind genera justify-items-center, justify-self-end, etc., y uno espera que funcionen como items-* y self-* en el otro eje. En un contenedor flex se ignoran por completo:

<div class="marco flex justify-items-center gap-2">...</div>
<div class="marco flex gap-2">
  <div class="caja w-16">1</div>
  <div class="caja w-16">2</div>
  <div class="caja w-16 justify-self-end bg-orange-500">3</div>
</div>
Posición de las tres cajas en los dos contenedores: x = 6, 78 y 150 px
Sin esas clases la posición es la misma.

Tiene una razón: en el eje transversal, cada elemento dispone de un hueco propio dentro de la línea. En el eje principal, todos comparten la línea, y no existe una celda en la que mover uno solo. Estas clases son para CSS Grid. Para mover un elemento en el eje principal usa márgenes automáticos.

place-* en un contenedor flex

Las clases place-* son atajos que fijan los dos ejes a la vez, y se pensaron para Grid. En flexbox funcionan a medias, porque una de sus dos mitades se ignora o depende de flex-wrap.

Clase Propiedad Equivale a En un contenedor flex
place-content-* place-content align-content + justify-content Actúa justify-content; align-content solo con flex-wrap
place-items-* place-items align-items + justify-items Solo actúa align-items
place-self-* place-self align-self + justify-self Solo actúa align-self
<div class="marco flex h-28 place-content-center items-start gap-2">...</div>
<div class="marco flex h-28 place-items-center gap-2">...</div>
<div class="marco flex h-28 flex-wrap place-content-center gap-2">...</div>
<div class="marco flex h-28 items-start gap-2">
  <div class="caja w-16">1</div>
  <div class="caja w-16">2</div>
  <div class="caja w-16 place-self-end bg-orange-500">3</div>
</div>

Cuatro contenedores que muestran qué mitad de cada clase place actúa

Figura 29. La única que centra de verdad en los dos ejes es flex-wrap place-content-center.

Hay además una trampa con los extremos. place-content-end genera place-content: end, y end no es lo mismo que flex-end: end se refiere al final en el sentido del texto, mientras que flex-end se refiere al final del eje flex. Con una dirección invertida el resultado es distinto:

<div class="marco flex flex-row-reverse justify-end gap-2">...</div>
<div class="marco flex flex-row-reverse place-content-end gap-2">...</div>
flex-row-reverse + justify-end        cajas a la izquierda (x = 6)
flex-row-reverse + place-content-end  cajas a la derecha (x = 394)

💡 Consejo: En flexbox, olvídate de place-*. Escribe items-center justify-center, que hace lo que dice en cualquier dirección. Deja place-* para Grid.

Alineación segura 🆕 v4.1

🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: usa una propiedad arbitraria, [justify-content:safe_center] o [align-items:safe_center]. Compila y funciona igual, con los mismos requisitos de navegador. Para no depender del navegador, usa márgenes automáticos: first:ms-auto last:me-auto en los hijos.

Centrar tiene un efecto secundario peligroso: si el contenido es más ancho que el contenedor, se desborda por los dos lados. Y lo que se desborda por el principio queda fuera del área de desplazamiento: no hay barra que te permita llegar.

Imagina unas categorías en una barra con desplazamiento horizontal:

<nav class="marco flex justify-center gap-2 overflow-x-auto">
  <a href="#" class="caja shrink-0">Portátiles</a>
  <a href="#" class="caja shrink-0">Sobremesa</a>
  <a href="#" class="caja shrink-0">Periféricos</a>
  <a href="#" class="caja shrink-0">Monitores</a>
  <a href="#" class="caja shrink-0">Almacenamiento</a>
  <a href="#" class="caja shrink-0">Redes</a>
  <a href="#" class="caja shrink-0">Software</a>
</nav>

La primera fila de la figura muestra el problema. “Portátiles” empieza en x = -119 px y el navegador solo permite desplazarse hacia la derecha: ese enlace no se puede alcanzar de ninguna forma. La zona desplazable mide 561 px, cuando el contenido necesita 686.

La alineación segura lo resuelve: justify-center-safe centra mientras quepa y, si no cabe, se alinea al principio.

<nav class="marco flex justify-center-safe gap-2 overflow-x-auto">...</nav>
.justify-center-safe { justify-content: safe center; }
.items-center-safe { align-items: safe center; }

Tres barras: justify-center corta el principio; justify-center-safe empieza por Portátiles; con pocos elementos centra

Figura 30. Arriba, “Portátiles” y parte de “Sobremesa” son inalcanzables. Abajo, con sitio de sobra, justify-center-safe centra igual que justify-center.

Pasa lo mismo en vertical. Una lista en columna centrada con más elementos de los que caben pierde los primeros:

<div class="marco flex h-40 flex-col justify-center gap-1 overflow-y-auto">...</div>
<div class="marco flex h-40 flex-col justify-center-safe gap-1 overflow-y-auto">...</div>

A la izquierda la lista empieza por el Elemento 3 y no se puede subir más; a la derecha empieza por el Elemento 1

Figura 31. A la izquierda, por mucho que subas, no llegas a ver el Elemento 1.

Y también con items-*, sin barras de desplazamiento de por medio:

<div class="marco ml-12 flex w-56 flex-col items-center">
  <div class="caja w-80 shrink-0">Portátil UltraBook 14 con garantía ampliada</div>
</div>
<div class="marco ml-12 flex w-56 flex-col items-center-safe">
  <div class="caja w-80 shrink-0">Portátil UltraBook 14 con garantía ampliada</div>
</div>

Arriba la caja sobresale por los dos lados del marco; abajo solo por la derecha

Figura 32. Si el contenedor estuviera pegado al borde de la pantalla, los 48 px que sobresalen a la izquierda se perderían.

Las clases seguras existentes en 4.3.3 son:

Familia Clases CSS
justify-* justify-center-safe, justify-end-safe safe center, safe flex-end
items-* items-center-safe, items-end-safe safe center, safe flex-end
self-* self-center-safe, self-end-safe safe center, safe flex-end
content-* content-center-safe, content-end-safe safe center, safe flex-end
place-* place-content-*-safe, place-items-*-safe, place-self-*-safe safe center, safe end

No hay versión segura de *-start, porque alinear al principio nunca oculta nada. Las versiones justify-items-*-safe y justify-self-*-safe también existen, pero son para Grid.

Soporte de navegadores: un detalle que importa

Esta función tiene una trampa que no está en las notas de la versión. Tailwind 4 exige Safari 16.4 o superior, pero según los datos de MDN (browser-compat-data), la palabra safe llegó en Chrome 115, Firefox 63 y Safari 17.6. Hay, por tanto, una franja de navegadores compatibles con Tailwind 4 que no entienden safe.

Un navegador que no conoce un valor descarta la declaración entera, y con justify-center-safe eso significa que no se aplica ninguna alineación: quedaría el valor por defecto, al principio del contenedor. Es un fallo tolerable, porque no se pierde contenido, pero con pocos elementos no estarían centrados. Si tu público incluye esas versiones, la alternativa que funciona en todos los navegadores son los márgenes automáticos, que además sirven tal cual en v3:

<nav class="marco flex gap-2 overflow-x-auto">
  <a href="#" class="caja shrink-0 first:ms-auto last:me-auto">Portátiles</a>
  <a href="#" class="caja shrink-0 first:ms-auto last:me-auto">Sobremesa</a>
  <!-- ... -->
</nav>

Funciona porque los márgenes auto solo absorben espacio positivo: si sobra, centran; si falta, valen cero y el contenido empieza al principio. Medido en Chromium, con pocos elementos el resultado cae a 143 px, el mismo sitio que justify-center-safe, y con muchos empieza en 6 px y deja desplazarse los 686 px.

Estas medidas son de Chromium. Si tu público usa Safari anterior a la 17.6, pruébalo en un dispositivo real.


8. Patrones reales

Todo lo anterior se combina en un puñado de composiciones que aparecen en casi cualquier interfaz.

Barra de navegación

<header class="flex items-center gap-6 rounded-lg bg-slate-900 px-4 py-3 text-white">
  <a href="#" class="text-lg font-bold">EMPRESA</a>
  <nav class="flex gap-4 text-sm text-slate-300">
    <a href="#">Catálogo</a>
    <a href="#">Ofertas</a>
    <a href="#">Soporte</a>
  </nav>
  <div class="ml-auto flex items-center gap-3">
    <span class="text-sm text-slate-300">Ana García</span>
    <button class="rounded-md bg-indigo-500 px-3 py-1.5 text-sm font-medium">Salir</button>
  </div>
</header>

Barra oscura con el logotipo, tres enlaces y, a la derecha, el usuario y el botón Salir

Figura 33. Tres contenedores flex anidados. ml-auto separa los dos grupos sin necesidad de justify-between.

Los contenedores flex se anidan sin límite: un elemento flex puede ser a la vez contenedor de los suyos. Aquí, <nav> y el último <div> son las dos cosas.

Tarjetas con el pie alineado

Es un clásico: tres tarjetas de la misma altura, con textos de distinta longitud y el precio y el botón alineados abajo.

<div class="flex gap-4">
  <article class="flex w-56 flex-col rounded-xl border border-gray-200 bg-white p-4">
    <div class="mb-3 flex h-24 items-center justify-center rounded-lg bg-indigo-100 text-sm font-medium text-indigo-700">Foto</div>
    <h3 class="font-bold">Portátil UltraBook 14</h3>
    <p class="mt-1 text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM y SSD de 512 GB con carcasa de aluminio</p>
    <div class="mt-auto flex items-center justify-between pt-4">
      <span class="text-lg font-bold">899 €</span>
      <button class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white">Añadir</button>
    </div>
  </article>
  <!-- Las otras dos tarjetas son iguales -->
</div>

Tres tarjetas de igual altura con el precio y el botón en la misma línea

Figura 34. El precio y el botón quedan alineados, sea cual sea la longitud de la descripción.

Si quitas mt-auto, cada pie se queda justo debajo de su texto:

Las mismas tarjetas sin mt-auto: los precios quedan a distintas alturas

Figura 35. Sin mt-auto, las tarjetas siguen teniendo la misma altura, pero el pie no baja.

El patrón necesita tres piezas:

  1. La fila exterior estira las tarjetas (no hace falta escribirlo: es el stretch por defecto).
  2. Cada tarjeta es una columna flex, con flex flex-col.
  3. El pie lleva mt-auto, que absorbe el espacio sobrante y lo empuja abajo.

Fila de datos

También llamado media object: un elemento rígido a cada lado y un texto flexible en el centro.

<ul class="divide-y divide-gray-200">
  <li class="flex items-center gap-3 py-2">
    <span class="flex size-10 shrink-0 items-center justify-center rounded-full bg-indigo-100 text-sm font-bold text-indigo-700">LM</span>
    <div class="min-w-0 flex-1">
      <p class="truncate text-sm font-medium">Luis Martínez Ruiz</p>
      <p class="truncate text-xs text-gray-500">Director comercial de la delegación de Andalucía y Extremadura</p>
    </div>
    <span class="shrink-0 text-sm font-semibold">4200 €</span>
  </li>
</ul>

Lista de tres empleados con avatar, nombre, cargo truncado y salario alineado a la derecha

Figura 36. shrink-0 en los extremos y min-w-0 flex-1 en el centro. Esta receta sirve para casi cualquier lista.

Fíjate en que el avatar también es un contenedor flex: flex items-center justify-center es la forma habitual de centrar algo en los dos ejes.

Grupo de campo y botón

<form class="flex rounded-lg border border-gray-300">
  <span class="flex items-center rounded-l-lg border-r border-gray-300 bg-gray-50 px-3 text-sm text-gray-500">€</span>
  <input type="number" value="899" class="min-w-0 flex-1 px-3 py-2 text-sm">
  <button class="shrink-0 rounded-r-lg bg-indigo-600 px-4 text-sm font-medium text-white">Actualizar</button>
</form>

Campo numérico con el símbolo del euro a la izquierda y el botón Actualizar a la derecha

Figura 37. El stretch por defecto iguala la altura de las tres piezas. El min-w-0 es necesario porque un <input> tiene un ancho mínimo propio.

Etiquetas

<div class="flex flex-wrap gap-2">
  <span class="inline-flex items-center gap-1.5 rounded-full bg-green-100 px-2.5 py-1 text-xs font-medium text-green-800">
    <span class="size-1.5 rounded-full bg-current"></span>
    Pagado
  </span>
  <!-- Pendiente, Cancelado, Sin stock -->
</div>

Cuatro etiquetas de estado con un punto de color

Figura 38. inline-flex para que cada etiqueta mida lo que su texto, y flex-wrap para que salten de línea.

Tarjeta que cambia de dirección

Pasar de columna a fila con un punto de ruptura es probablemente el uso más común de flexbox en una web adaptable. Los prefijos como md: se estudian en Diseño responsive: por ahora basta con saber que md:flex-row se aplica desde 768 px de ancho.

<article class="flex flex-col gap-4 rounded-xl border border-gray-200 p-4 md:flex-row md:items-center">
  <div class="flex h-32 shrink-0 items-center justify-center rounded-lg bg-indigo-100 text-sm font-medium text-indigo-700 md:h-24 md:w-40">Foto</div>
  <div class="min-w-0 flex-1">
    <h3 class="font-bold">Portátil UltraBook 14</h3>
    <p class="mt-1 text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
  </div>
  <div class="flex items-center justify-between md:flex-col md:items-end md:gap-2">
    <span class="text-lg font-bold">899 €</span>
    <button class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white">Añadir</button>
  </div>
</article>

La tarjeta con 380 píxeles de ancho: foto arriba y texto y precio debajo

Figura 39. A 380 px, tres bloques apilados.

La misma tarjeta con 820 píxeles de ancho: foto, texto y precio en una fila

Figura 40. A 820 px, el mismo HTML en una fila.

Pie pegado al fondo

Si una página tiene poco contenido, el pie sube y queda flotando en mitad de la pantalla. Con una columna flex de al menos toda la altura se resuelve:

<body class="flex min-h-dvh flex-col">
  <header class="bg-slate-900 px-4 py-3 font-bold text-white">EMPRESA</header>
  <main class="flex-1 p-4 text-sm">
    <h1 class="text-xl font-bold">Política de devoluciones</h1>
    <p class="mt-2">Dispones de 14 días para devolver tu pedido.</p>
  </main>
  <footer class="bg-gray-100 px-4 py-3 text-sm text-gray-600">EMPRESA S.A.</footer>
</body>

Página con cabecera, poco contenido y el pie pegado al borde inferior

Figura 41. main con flex-1 se lleva todo el sobrante.

Es importante usar min-h-dvh y no h-dvh: con min-h-* la página crece si hay mucho contenido. Con h-dvh el contenido se desbordaría sobre el pie. Las unidades dvh se explican en el módulo 03.

Panel con barra lateral

Una aplicación de gestión: barra lateral fija, cabecera fija y solo el contenido con desplazamiento.

<div class="flex h-dvh">
  <aside class="flex w-52 shrink-0 flex-col gap-1 bg-slate-900 p-3 text-sm text-slate-200">
    <p class="mb-2 text-lg font-bold text-white">EMPRESA</p>
    <a href="#" class="rounded-md bg-slate-700 px-3 py-2">Pedidos</a>
    <a href="#" class="rounded-md px-3 py-2">Productos</a>
    <a href="#" class="rounded-md px-3 py-2">Clientes</a>
    <p class="mt-auto px-3 text-xs text-slate-400">Ana García</p>
  </aside>
  <div class="flex min-w-0 flex-1 flex-col">
    <header class="flex items-center justify-between border-b border-gray-200 px-4 py-3">
      <h1 class="font-bold">Pedidos</h1>
      <button class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white">Nuevo</button>
    </header>
    <main class="flex-1 overflow-y-auto p-4 text-sm">
      <div class="flex flex-col gap-2">
        <div class="rounded-md border border-gray-200 p-2">Pedido 5001</div>
        <!-- ...muchos pedidos -->
      </div>
    </main>
  </div>
</div>

Panel con barra lateral oscura, cabecera fija y una lista de pedidos desplazada

Figura 42. La lista está desplazada, pero la barra lateral y la cabecera siguen en su sitio.

Aquí hay aplicadas casi todas las ideas del módulo: shrink-0 en la barra, flex-1 en el contenido, mt-auto para llevar el usuario abajo y una columna anidada. Medido en una ventana de 420 px de alto: el documento sigue midiendo 420 px, mientras que el main muestra 363 px de los 1404 de su contenido. El main no necesita min-h-0 porque tiene overflow-y-auto.


9. Flexbox o Grid

Tienen mucho en común, pero piensan de forma distinta: en flexbox manda el contenido y en Grid manda la rejilla.

Un catálogo con flex-wrap deja ver la diferencia:

<div class="flex flex-wrap gap-3">
  <article class="min-w-48 flex-1 rounded-lg border border-gray-200 p-3">
    <h3 class="text-sm font-bold">Portátil Gaming 16</h3>
    <p class="text-sm text-gray-600">1499 €</p>
  </article>
  <!-- ...cuatro más -->
</div>

Tres tarjetas en la primera fila y dos más anchas en la segunda

Figura 43. Cada línea reparte su espacio por su cuenta: las columnas de una fila no se alinean con las de la otra.

La segunda fila tiene dos tarjetas y se reparte el ancho entero. Es el comportamiento correcto de flexbox, y a la vez el motivo por el que un catálogo suele hacerse con Grid.

Situación Elige
Elementos en una línea: barras, botones, etiquetas Flexbox
El tamaño depende del contenido Flexbox
Empujar un elemento al extremo Flexbox
Centrar algo en los dos ejes Cualquiera
Filas y columnas alineadas entre sí Grid
Catálogos con tarjetas iguales Grid
Estructura general de la página Grid, con flexbox dentro

No son rivales: lo normal es usar Grid para la estructura y flexbox para el interior de cada pieza.


10. Depuración

Las herramientas de desarrollo muestran una etiqueta flex junto a cada contenedor en el panel de elementos. Al pulsarla, el navegador dibuja sobre la página los huecos y el espacio libre. En Chrome, además, la sección Layout lista todos los contenedores flex de la página, y la pestaña Computed te da el tamaño final de cada elemento.

Síntoma Causa probable Solución
truncate no funciona El padre flex no puede encoger min-w-0 en el elemento flex
El elemento se deforma o aplasta Está encogiendo shrink-0
Una imagen se estira stretch por defecto items-start o self-start
justify-* no hace nada Algún elemento tiene grow o flex-1: no sobra espacio Quita el grow o usa márgenes automáticos
items-center no centra en vertical El contenedor no tiene más altura que su contenido Da altura al contenedor (h-*, min-h-*)
justify-center centra en vertical Estás en flex-col Usa items-center
items-stretch no iguala El elemento tiene h-* Quita la altura
content-* no hace nada Falta flex-wrap Añádelo
justify-self-* no hace nada Es para Grid ml-auto o mr-auto
w-1/2 no da la mitad Están encogiendo o creciendo shrink-0, o revisa flex-*
Tres basis-1/3 no caben con gap Los huecos se suman al 100 % flex-1 o Grid
El principio se corta con justify-center Desbordamiento justify-center-safe
Un hijo no responde a las clases flex No es hijo directo Mueve flex a su padre

💡 Consejo: Cuando no entiendas un reparto, añade temporalmente outline outline-red-500 a los elementos: ves sus cajas reales sin que el contorno ocupe sitio ni altere los tamaños.


11. Ejercicios

Ejercicio 1: Cabecera de la tienda

Crea una cabecera con el logotipo EMPRESA a la izquierda, un buscador en el centro que ocupe todo el sitio disponible y “Carrito (3)” a la derecha. Si la ventana se estrecha, el logotipo y el carrito no deben encoger.

Ver solución
<header class="flex items-center gap-4 rounded-lg bg-white p-3 shadow">
  <a href="#" class="shrink-0 text-lg font-bold text-indigo-700">EMPRESA</a>
  <input type="search" placeholder="Buscar productos" class="min-w-0 flex-1 rounded-md border border-gray-300 px-3 py-1.5 text-sm">
  <a href="#" class="shrink-0 text-sm font-medium">Carrito (3)</a>
</header>

Cabecera con logotipo, buscador ancho y carrito

Figura 44. El buscador se lleva todo el espacio libre.

flex-1 hace que el buscador se lleve el espacio sobrante. min-w-0 es necesario porque un <input> tiene un ancho mínimo propio; sin él, en pantallas estrechas no encogería y empujaría la cabecera fuera del contenedor. shrink-0 protege los extremos.

Ejercicio 2: Calcula el crecimiento

Un contenedor de 800 px, sin gap, tiene tres elementos sin relleno:

  • A: basis-[200px] grow
  • B: basis-[200px] grow
  • C: basis-[100px] grow-2

Calcula el ancho final de cada uno antes de comprobarlo.

Ver solución
Espacio libre: 800 - (200 + 200 + 100) = 300 px
Unidades de grow: 1 + 1 + 2 = 4
Cada unidad: 300 / 4 = 75 px

A: 200 + 75  = 275 px
B: 200 + 75  = 275 px
C: 100 + 150 = 250 px

Medido en el navegador: 275, 275 y 250 px. C tiene el doble de grow y aun así es el más estrecho, porque grow reparte lo libre y C parte de una base menor. En la LTS, escribe grow-[2].

Ejercicio 3: Calcula el encogimiento

Un contenedor de 400 px tiene dos elementos sin relleno:

  • A: basis-[300px]
  • B: basis-[200px] shrink-3

Calcula el ancho final de cada uno.

Ver solución
Exceso: 300 + 200 - 400 = 100 px
Pesos: A = 1 × 300 = 300; B = 3 × 200 = 600; suma = 900

A cede 100 × 300 / 900 = 33,33 px  →  266,67 px
B cede 100 × 600 / 900 = 66,67 px  →  133,33 px

Medido en el navegador: 266,67 y 133,33 px. Compruébalo con cajas sin relleno, porque, como viste en la sección 5, el peso se calcula sobre el tamaño sin relleno:

<div class="flex" style="width: 400px">
  <div class="h-9 basis-[300px] bg-indigo-500">A</div>
  <div class="h-9 shrink-3 basis-[200px] bg-orange-500">B</div>
</div>

En la LTS, escribe shrink-[3]. El resultado medido es el mismo.

Ejercicio 4: Fila de pedido

Crea una fila con el número de pedido, la lista de productos y el importe. La lista debe truncarse con puntos suspensivos, y el número y el importe deben verse siempre enteros.

Ver solución
<div class="flex items-center gap-3 rounded-lg border border-gray-200 p-3">
  <span class="shrink-0 font-mono text-sm text-gray-500">#5001</span>
  <p class="flex-1 truncate text-sm font-medium">Portátil UltraBook 14, ratón inalámbrico, teclado mecánico y monitor</p>
  <span class="shrink-0 text-sm font-bold">1327,80 €</span>
</div>

Aquí no hace falta min-w-0, porque el elemento flex es el propio <p> y truncate incluye overflow: hidden. Si envolvieras el <p> en un <div>, sería ese <div> el que lo necesitaría.

Ejercicio 5: Tarjetas de departamento

Crea tres tarjetas de igual ancho y altura para VENTAS, INFORMÁTICA y LOGÍSTICA, con descripciones de distinta longitud y un botón “Ver equipo” alineado abajo. El botón debe medir lo que su texto, no todo el ancho de la tarjeta.

Ver solución
<div class="flex gap-4">
  <article class="flex flex-1 flex-col gap-2 rounded-xl border border-gray-200 p-4">
    <h3 class="font-bold">VENTAS</h3>
    <p class="text-sm text-gray-600">Atiende a los clientes y gestiona los pedidos de todas las delegaciones de la empresa.</p>
    <a href="#" class="mt-auto self-start rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white">Ver equipo</a>
  </article>
  <!-- INFORMÁTICA y LOGÍSTICA igual -->
</div>

Tres tarjetas iguales con el botón alineado abajo

Figura 45. Mismo ancho, misma altura y botones alineados.

flex-1 iguala los anchos, y el stretch por defecto las alturas. mt-auto baja el botón, y self-start evita que se estire a lo ancho: dentro de una columna, el stretch por defecto actúa en horizontal. Sin self-start, el enlace ocuparía todo el ancho.

Ejercicio 6: Página de acceso

Centra un formulario de acceso en la pantalla, en los dos ejes, con un ancho máximo y los campos apilados.

Ver solución
<main class="flex min-h-dvh items-center justify-center p-4">
  <form class="flex w-full max-w-sm flex-col gap-3 rounded-xl bg-white p-6 shadow">
    <h1 class="text-center text-xl font-bold">Acceso a EMPRESA</h1>
    <input type="email" placeholder="Correo" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
    <input type="password" placeholder="Contraseña" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
    <button class="rounded-md bg-indigo-600 py-2 text-sm font-medium text-white">Entrar</button>
  </form>
</main>

Formulario de acceso centrado en una página gris

Figura 46. Centrado en los dos ejes con items-center justify-center.

Medido en una ventana de 560 × 420 px: 88 px de margen a cada lado y 98 px arriba y abajo. w-full max-w-sm es la combinación habitual: ocupa todo el ancho en móvil y se limita en pantallas grandes. Sin w-full, el formulario mediría solo lo que su contenido, porque ya no se estira en ese eje. Y min-h-dvh da la altura que items-center necesita para que se note.

Ejercicio 7: Cabecera que se reorganiza

En móvil, el logotipo y el carrito van en la primera fila, y debajo el buscador a todo el ancho. Desde md, los tres van en la misma fila con el buscador en el centro. En el HTML, el buscador debe estar entre el logotipo y el carrito.

Ver solución
<header class="flex flex-wrap items-center justify-between gap-3 rounded-lg bg-white p-3 shadow">
  <a href="#" class="text-lg font-bold text-indigo-700">EMPRESA</a>
  <input type="search" placeholder="Buscar productos" class="order-last min-w-0 basis-full rounded-md border border-gray-300 px-3 py-1.5 text-sm md:order-none md:flex-1">
  <a href="#" class="text-sm font-medium">Carrito (3)</a>
</header>

Cabecera a 380 px: logotipo y carrito arriba, buscador abajo

Figura 47. A 380 px, basis-full lleva el buscador a su propia línea.

Cabecera a 820 px: todo en una fila

Figura 48. A 820 px, el mismo HTML en una fila.

En móvil, basis-full obliga al buscador a pedir el 100 % de la línea, y order-last lo manda al final. Con flex-wrap, salta a una segunda fila. Desde md, md:order-none lo devuelve a su sitio y md:flex-1 sustituye la base por cero al ser un atajo que fija los tres valores. Medido: el buscador está en y = 68 px con 324 px de ancho a 380 px de ventana, y en y = 28 px con 594 px a 820 px.

Este es un buen uso de order: el tabulador recorre logotipo, buscador y carrito, y el cambio visual en móvil apenas afecta a la lectura.

Ejercicio 8: Pestañas seguras

Crea una barra de categorías centrada con desplazamiento horizontal. Si caben, deben verse centradas; si no, debe poder llegarse a la primera. Escribe además la versión para la LTS.

Ver solución
<nav class="flex justify-center-safe gap-2 overflow-x-auto rounded-lg bg-gray-100 p-2">
  <a href="#" class="shrink-0 rounded-md bg-white px-3 py-1.5 text-sm font-medium">Portátiles</a>
  <a href="#" class="shrink-0 rounded-md bg-white px-3 py-1.5 text-sm font-medium">Sobremesa</a>
  <a href="#" class="shrink-0 rounded-md bg-white px-3 py-1.5 text-sm font-medium">Periféricos</a>
  <!-- ... -->
</nav>

Barra de categorías que empieza por Portátiles y se corta por la derecha

Figura 49. Con desbordamiento, justify-center-safe empieza por el primer enlace.

shrink-0 es imprescindible: sin él, los enlaces encogerían en lugar de desbordarse. En la LTS, cambia justify-center-safe por [justify-content:safe_center], o quita esa clase y añade first:ms-auto last:me-auto a los enlaces, que funciona también en los navegadores sin safe.

Ejercicio 9 (propuesto): Conversación

Crea una conversación entre un cliente y soporte. Cada mensaje es una burbuja de ancho máximo max-w-xs; los del cliente se alinean a la derecha y los de soporte a la izquierda. Pista: el contenedor es una columna, y self-end en una columna empuja al final de la línea. Comprueba después qué ocurre con dir="rtl".

Ejercicio 10 (propuesto): Barra de herramientas

Crea una barra con tres grupos de botones: a la izquierda Guardar y Deshacer, en el centro el título del documento y a la derecha Compartir. El título debe truncarse si no cabe y seguir centrado aunque los grupos laterales midan distinto. Pista: prueba flex-1 en los dos grupos laterales, y piensa por qué grow no serviría.

Ejercicio 11 (propuesto): Porta a la LTS

Reescribe para Tailwind 3.4 estas clases y compila el resultado en un proyecto v3: grow-3, shrink-2, flex-2, flex-1/2, order-13, basis-xs, basis-17, basis-(--ancho), items-baseline-last, justify-center-safe. Después, comprueba si space-x-4 con flex-wrap se comporta igual que en v4.


Resumen

Concepto API Idea clave
Contenedor flex, inline-flex Afecta solo a los hijos directos; hacia fuera, bloque o en línea
Dirección flex-row, flex-col, *-reverse Define el eje principal; *-reverse no cambia el orden del foco
Varias líneas flex-wrap, flex-nowrap, flex-wrap-reverse Por defecto, una sola línea, encogiendo lo que haga falta
Separación gap-*, gap-x-*, gap-y-* Preferible a space-*, que falla con flex-wrap
Tamaño de partida basis-* Gana a w-*; sigue el eje
Reparto del sobrante grow, grow-* 🔄 v4.0 Reparte lo libre, no el total
Reparto del exceso shrink, shrink-0, shrink-* 🔄 v4.0 Ponderado por el tamaño base sin relleno
Atajos flex-1, flex-auto, flex-initial, flex-none flex-1: base cero, partes iguales
Valores dinámicos 🆕 v4.0 flex-2, flex-1/2, basis-17 En v3, con corchetes
Protección de contenido min-w-0, min-h-0 Quitan el mínimo automático; necesarios para truncate en un hijo
Orden 🔄 v4.0 order-*, order-first, order-last Solo visual; el foco sigue el HTML
Eje principal justify-* Reparte el sobrante; sin sobrante no hace nada
Eje transversal items-* stretch por defecto iguala alturas
Línea base items-baseline, items-baseline-last 🆕 v4.1 Primera o última línea de texto
Un elemento self-* Sustituye a items-*; self-auto lo restaura
Entre líneas content-* Solo con flex-wrap
Mover uno en el eje principal ml-auto, mt-auto, mx-auto, ms-auto Absorben el espacio libre
Solo Grid justify-items-*, justify-self-* Se ignoran en flexbox
Atajos de dos ejes place-content-*, place-items-*, place-self-* Funcionan a medias; mejor items-center justify-center
Alineación segura 🆕 v4.1 justify-center-safe, items-center-safe… No oculta contenido al desbordar; requiere Safari 17.6
Patrones flex-1, shrink-0, min-w-0, mt-auto Con estas cuatro clases se montan casi todos