Módulo 08: Flexbox
Objetivos del módulo
- Entender el modelo de flexbox: contenedor, elementos, eje principal y eje transversal
- Elegir entre
flexeinline-flexy saber qué les cambia a los hijos directos - Controlar la dirección con
flex-rowyflex-col, y conocer el problema de accesibilidad de*-reverse - Partir una fila en varias con
flex-wrapy separar congap-*en lugar despace-* - Calcular a mano cómo reparten el espacio
basis-*,grow-*yshrink-* - Usar con criterio los atajos
flex-1,flex-auto,flex-initialyflex-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-*ycontent-*, y distinguir cuándo actúa cada uno - Saber por qué
justify-items-*yjustify-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-lasty 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.csssin 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.
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; }

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>

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 ygap-*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>

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
*-reverseyorder-*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>

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>

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>

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); }

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 conflex-wrapla segunda fila se desplaza. Si usas la LTS: usagap-*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>

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-*. Reservaspace-*para pilas de bloques normales, sinflex.
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; }

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-*obasis-*? En una fila dan casi el mismo resultado.basis-*tiene dos ventajas: sigue el eje, así que en unflex-col md:flex-rowsignifica 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
growygrow-0, ygrow-2ogrow-3no generan CSS. Si usas la LTS: escribe el valor entre corchetes,grow-[2]ygrow-[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>

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>
Figura 12. grow reparte el espacio libre, no el total.

Figura 13. El resultado real en el navegador: 150, 200 y 250 px, igual que en el cálculo.
⚠️ Importante:
grow-3no hace que el elemento mida el triple que uno congrow. 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 haceflex-1en la sección de atajos.
shrink-* 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) solo existen
shrinkyshrink-0, yshrink-2no 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>

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 -->

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-1para columnas que deben ser iguales, sea cual sea su contenido. - Usa
growpara 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 deflex-2yflex-[50%]en lugar deflex-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%); }

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/2no esbasis-1/2. Compila aflex: 50%, que significa1 1 50%: el elemento también crece y encoge.basis-1/2solo fija la base y mantienegrowa 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-xsescribebasis-[20rem]; parabasis-17,basis-[4.25rem]; para un nombre propio, añádelo entheme.extend.flexBasisdetailwind.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>

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-0va en el elemento flex, que es el hijo directo del contenedor. En el ejemplo, eldiv, no los<p>.- Si el propio elemento flex es el que lleva
truncate, no hace faltamin-w-0. La reglamin-width: autono se aplica a los elementos cuyooverflowno esvisible, ytruncateincluyeoverflow: 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>

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:
flex-1si son columnas iguales en una sola línea.- CSS Grid con
grid-cols-3 gap-4, que resta los huecos. - 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 hastaorder-12. Si usas la LTS: para valores mayores escribeorder-[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>

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.
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>

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>

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-10en el elemento gana aitems-stretch, y es la causa más común de que “stretchno 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>

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 sirveself-[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; }

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>

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>

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: concontent-endsin wrap, la caja sigue arriba (y = 6 px); al añadirflex-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, noitems-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>

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>

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>

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-*. Escribeitems-center justify-center, que hace lo que dice en cualquier dirección. Dejaplace-*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-autoen 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; }

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>

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>

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>

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>

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:

Figura 35. Sin mt-auto, las tarjetas siguen teniendo la misma altura, pero el pie no baja.
El patrón necesita tres piezas:
- La fila exterior estira las tarjetas (no hace falta escribirlo: es el
stretchpor defecto). - Cada tarjeta es una columna flex, con
flex flex-col. - 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>

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>

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>

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>

Figura 39. A 380 px, tres bloques apilados.

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>

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>

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>

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-500a 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>

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>

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>

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>

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

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>

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 |