Módulo 03: Espaciado y tamaño
Objetivos del módulo
- Relacionar cada capa del modelo de caja con la familia de clases que la controla
- Entender por qué
w-*incluye el relleno y el borde, y cuándo usarbox-content - Usar la escala de espaciado con soltura y saber qué valores existen en cada versión
- Aplicar relleno y margen a un lado, a un eje o a todos, sin dudar del sufijo
- Centrar con
mx-auto, empujar conms-autoy solapar con márgenes negativos - Elegir entre
gap-*,space-*y márgenes, y explicar por quégap-*suele ser la mejor opción - Controlar la anchura con la escala, fracciones, nombres y palabras clave (
w-fit,w-max…) - Saber por qué
h-fulla veces no hace nada - Distinguir
vh,svh,lvhydvh, y usarlos en móviles - Usar
size-*,min-*ymax-*, y resolver el clásicomin-w-0de un texto que desborda - Escribir interfaces que funcionen de izquierda a derecha y de derecha a izquierda con propiedades lógicas
- Maquetar una página completa de la tienda solo con lo aprendido
- Traducir cualquier clase de este módulo a Tailwind 3.4 si eliges esa versión
1. Preparación del laboratorio
Seguimos con el proyecto Vite del módulo 01. Basta con un app.css con @import "tailwindcss"; y una página como esta:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Espaciado</title>
<link rel="stylesheet" href="/app.css">
</head>
<body class="bg-white text-gray-900">
<!-- Aquí van los ejemplos -->
</body>
</html>
Arranca npm run dev y deja el navegador abierto. Recuerda el aviso del módulo 02: si creas un HTML con el servidor en marcha y una clase no aparece, guarda app.css para forzar un nuevo escaneo.
Algunos ejemplos colocan elementos con clases de otros módulos. Te basta con esta idea por ahora:
| Clases | Qué hacen | Dónde se estudian |
|---|---|---|
flex, grid, grid-cols-3, items-center | Colocan a los hijos en fila o en rejilla | Flexbox y Grid |
flex-1, shrink-0 | Reparten o protegen el espacio de un hijo flex | Flexbox |
text-sm, font-bold, truncate | Texto | Tipografía |
bg-indigo-200, text-gray-600 | Color | Color y fondos |
border, rounded-lg, shadow-sm | Bordes y sombras | Bordes, sombras y efectos |
sm:, lg: | Aplican una clase a partir de un ancho | Diseño responsive |
Si quieres repasar el modelo de caja desde cero, tienes el Curso de HTML y CSS.
2. El modelo de caja en clases de Tailwind
Todo elemento se dibuja como una caja de cuatro capas: contenido, relleno (padding), borde y margen. Casi todo este módulo consiste en poner valor a esas capas.
Figura 1. Cada capa de la caja tiene su familia de clases. La anchura w-* llega hasta el borde porque Preflight activa box-border.
| Capa | Clases | Propiedades CSS | Se pinta con el fondo |
|---|---|---|---|
| Relleno | p-* y variantes | padding | Sí |
| Borde | border-* | border | Tiene su propio color |
| Margen | m-* y variantes | margin | No, es transparente |
| Tamaño | w-*, h-*, size-*, min-*, max-* | width, height… | |
| Entre hijos | gap-*, space-* | gap, o márgenes en los hijos | No |
La diferencia práctica entre relleno y margen es sencilla: el relleno agranda la zona clicable y coloreada de un botón; el margen separa ese botón de lo que tiene alrededor. Si dudas, pregúntate si quieres que el fondo llegue hasta ahí.
box-border y box-content
Por defecto, CSS mide width solo hasta el contenido: el relleno y el borde se suman por fuera. Es incómodo, porque una caja de 100% con relleno se sale de su padre. Por eso Preflight aplica a todos los elementos box-sizing: border-box, como viste en el módulo 02. Las dos clases de esta familia cambian ese valor:
<div class="box-border w-48 border-8 border-indigo-600 bg-indigo-100 p-6">
<p class="font-mono text-sm">box-border</p>
</div>
<div class="box-content w-48 border-8 border-orange-500 bg-orange-100 p-6">
<p class="font-mono text-sm">box-content</p>
</div>
.box-border {
box-sizing: border-box;
}
.box-content {
box-sizing: content-box;
}

Figura 2. Las dos llevan w-48 (192 px). La primera mide 192 px en total; la segunda, 256 px.
Medidas con getBoundingClientRect() en el navegador: box-border da 192 px, y box-content da 192 + 2 × 24 de relleno + 2 × 8 de borde = 256 px.
Con Preflight activo, box-border no cambia nada: ya es el valor de todos los elementos. Te hará falta box-content en contadas ocasiones, por ejemplo cuando quieres que el contenido mida exactamente una cantidad y el relleno se añada alrededor. También es útil box-border si has desactivado Preflight.
💡 Consejo: Abre las herramientas del navegador, selecciona un elemento y mira el diagrama de la pestaña Computed. Muestra margen, borde, relleno y contenido con sus medidas reales. Es la forma más rápida de saber de dónde sale un espacio.
3. La escala de espaciado en la práctica
En el módulo 02 viste de dónde sale la escala: una variable --spacing de 0.25rem multiplicada por el número de la clase. Aquí nos interesa usarla.
| Clase | px | Uso habitual |
|---|---|---|
0.5, 1 | 2, 4 | Separación entre icono y texto |
2, 3 | 8, 12 | Relleno de botones y etiquetas |
4, 6 | 16, 24 | Relleno de tarjetas, separación de bloques |
8, 12 | 32, 48 | Separación entre secciones |
16, 24 | 64, 96 | Márgenes grandes de portada |
32 a 96 | 128 a 384 | Anchos y altos de elementos |
La misma escala sirve para p-, m-, gap-, space-, w-, h-, size-, min- y max-. Por eso w-4 y p-4 miden lo mismo.
📘 Concepto: Trabajar con una escala limita las opciones a propósito. Si todos los espacios salen de la misma serie, la interfaz parece ordenada sin que nadie lo decida en cada caso, y se acaban los debates entre
13pxy14px.
Multiplicadores libres 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) solo existen los valores de una lista cerrada, y
p-13ow-150no generan CSS. Si usas la LTS: usa un valor arbitrario comop-[3.25rem], o añade el valor atheme.extend.spacingentailwind.config.js.
En 4.x el número es un multiplicador libre, siempre que sea múltiplo de 0.25. Este es el resultado de compilar cada clase en las dos versiones:
| Clase | Tailwind 3.4.19 | Tailwind 4.3.3 |
|---|---|---|
p-13 | No genera CSS | calc(var(--spacing) * 13) |
p-1.25 | No genera CSS | calc(var(--spacing) * 1.25) |
p-1000 | No genera CSS | calc(var(--spacing) * 1000) |
p-1.3 | No genera CSS | No genera CSS |
La lista cerrada de 3.4 es esta, y conviene conocerla aunque uses 4.x, porque es la serie que mantiene el ritmo visual:
0 px 0.5 1 1.5 2 2.5 3 3.5 4 5 6 7 8 9 10 11 12 14 16 20 24 28 32 36 40 44 48 52 56 60 64 72 80 96

Figura 3. La escala aplicada a w-*. Las barras naranjas (w-13 y w-150) solo existen en 4.x.
⚠️ Importante: Que puedas escribir
p-13no significa que debas. Si usas todos los números, pierdes la ventaja de la escala. Reserva los valores que no están en la serie para los casos en que un diseño los pida de verdad.
Densidad local con --spacing 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) las utilidades llevan el valor escrito (
padding: 1.5rem), así que no se puede reescalar una zona de la página. Si usas la LTS: no hay equivalente directo; define variantes a mano con clases distintas (p-6frente ap-3) para cada densidad.
Como .p-6 es padding: calc(var(--spacing) * 6), y las variables se heredan, puedes redefinir la unidad en un elemento con una propiedad arbitraria. Todo lo que hay dentro se adapta:
<article class="w-64 rounded-lg border border-gray-200 p-6">
<h3 class="font-bold">Portátil UltraBook 14</h3>
<p class="mt-2 text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="mt-4 font-bold">899 €</p>
</article>
<article class="w-64 rounded-lg border border-gray-200 p-6 [--spacing:0.15rem]">
<h3 class="font-bold">Portátil UltraBook 14</h3>
<p class="mt-2 text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="mt-4 font-bold">899 €</p>
</article>

Figura 4. La misma tarjeta con --spacing por defecto y con 0.15rem. El relleno pasa de 24 a 14,4 px.
Fíjate en un efecto secundario: la segunda tarjeta también es más estrecha, porque w-64 usa la misma variable y pasa de 256 a 153,6 px. Si quieres un modo compacto que conserve el ancho, fija el ancho con algo que no dependa de la escala, como un valor arbitrario o un ancho con nombre (sección 8). Es una técnica potente para tablas y paneles densos, pero úsala con cuidado.
4. Relleno: p-*
Los sufijos
Tanto p- como m- admiten el mismo juego de sufijos:
Figura 5. Los sufijos de lado de las familias p- y m-.
| Clase | Lado | CSS en 4.3 |
|---|---|---|
p-4 | Los cuatro | padding |
px-4 | Eje horizontal | padding-inline |
py-4 | Eje vertical | padding-block |
pt-4, pr-4, pb-4, pl-4 | Arriba, derecha, abajo, izquierda | padding-top… |
ps-4, pe-4 | Inicio y fin del texto | padding-inline-start, padding-inline-end |
pbs-4, pbe-4 | Inicio y fin del bloque | padding-block-start, padding-block-end (sección 13) |
<div class="bg-indigo-300 p-4"><div class="bg-white text-center font-mono text-sm">p-4</div></div>
<div class="bg-indigo-300 px-6"><div class="bg-white text-center font-mono text-sm">px-6</div></div>
<div class="bg-indigo-300 py-6"><div class="bg-white text-center font-mono text-sm">py-6</div></div>
<div class="bg-indigo-300 px-8 py-2"><div class="bg-white text-center font-mono text-sm">px-8 py-2</div></div>
<div class="bg-indigo-300 pt-6"><div class="bg-white text-center font-mono text-sm">pt-6</div></div>
<div class="bg-indigo-300 pr-6"><div class="bg-white text-center font-mono text-sm">pr-6</div></div>
<div class="bg-indigo-300 pb-6"><div class="bg-white text-center font-mono text-sm">pb-6</div></div>
<div class="bg-indigo-300 pl-6"><div class="bg-white text-center font-mono text-sm">pl-6</div></div>

Figura 6. El azul es el relleno y el blanco, el contenido.
Lo específico gana a lo general
Puedes combinar clases de distinta amplitud, sin importar el orden en que las escribas. Comprobado en el navegador con class="p-4 px-2 pl-1":
padding-top: 16px (viene de p-4)
padding-right: 8px (viene de px-2)
padding-left: 4px (viene de pl-1)
Tailwind ordena la hoja de lo más amplio a lo más concreto, así que pl-1 queda detrás de px-2, y este detrás de p-4. Es el mismo principio del módulo 02: el orden del atributo class no decide nada, lo decide el orden en la hoja.
px-* y py-* son lógicas 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
px-4generapadding-leftypadding-right, y lo mismo ocurre conpy-*,mx-*ymy-*. Si usas la LTS: en escritura horizontal el resultado es idéntico; solo cambia en escritura vertical, donde debes usar[padding-inline:1rem]para el mismo efecto.
/* Tailwind 3.4 */
.px-4 { padding-left: 1rem; padding-right: 1rem; }
/* Tailwind 4.3 */
.px-4 { padding-inline: calc(var(--spacing) * 4); }
En una página en español, o en árabe, no notarás nada: el eje inline sigue siendo horizontal. Solo cambia con writing-mode: vertical-rl, que verás en la sección 13.
Patrones habituales de relleno
| Elemento | Clases | Por qué |
|---|---|---|
| Botón | px-4 py-2 | Más relleno horizontal: el texto respira y la altura sigue baja |
| Etiqueta | px-2 py-0.5 | Compacta |
| Tarjeta | p-6 | Igual por los cuatro lados |
| Celda de tabla | px-4 py-3 | Las filas se leen mejor con aire vertical |
| Sección de página | px-4 py-8 | Separación lateral en móvil, vertical amplia |
⚠️ Importante: No hay relleno negativo.
-p-4no genera CSS en ninguna versión, ni tampoco-gap-2o-w-4. El estándar CSS no los permite. Solo el margen puede ser negativo.
5. Margen: m-*
Los sufijos son los mismos: m, mx, my, mt, mr, mb, ml, ms, me. Lo interesante del margen son tres cosas que no tiene el relleno: auto, los negativos y el colapso.
Centrar y empujar con auto
Un margen auto se queda con todo el espacio libre. Con eso se resuelven dos patrones muy frecuentes:
<!-- Centrar un bloque: necesita una anchura menor que la del padre -->
<div class="mx-auto w-48 bg-indigo-200 p-2 font-mono text-sm">mx-auto</div>
<!-- Pegar un bloque a la derecha -->
<div class="ml-auto w-48 bg-indigo-200 p-2 font-mono text-sm">ml-auto</div>
<!-- En un flex: separar un grupo del resto -->
<div class="flex items-center bg-gray-100 p-2">
<span class="font-bold">EMPRESA</span>
<a href="#" class="ml-auto text-indigo-700">Catálogo</a>
<a href="#" class="ml-4 text-indigo-700">Carrito</a>
</div>

Figura 7. mx-auto centra el bloque, ml-auto lo empuja a la derecha, y en un contenedor flex separa grupos sin elementos vacíos.
El CSS ya lo conoces, aunque cambia ligeramente de una versión a otra:
/* Tailwind 3.4 */
.mx-auto { margin-left: auto; margin-right: auto; }
/* Tailwind 4.3 */
.mx-auto { margin-inline: auto; }
⚠️ Importante:
mx-autono hace nada en dos casos muy habituales. En un bloque sin anchura, porque ya ocupa todo y no queda espacio libre que repartir. Y en un elemento en línea como<span>, porque los márgenes horizontalesautono centran elementos en línea. Si no se centra, comprueba primero que tengaw-*omax-w-*y que sudisplayseablock.
Márgenes negativos
Se escriben con un guion delante de toda la clase, no del número. Sirven para sacar un elemento de su sitio y solaparlo con otro:
<article class="rounded-lg border border-gray-200">
<div class="h-24 rounded-t-lg bg-indigo-600"></div>
<div class="px-6 pb-6">
<div class="-mt-10 size-20 rounded-full border-4 border-white bg-orange-400"></div>
<h3 class="mt-2 font-bold">Isabel Torres</h3>
<p class="text-sm text-gray-600">Directora de Ventas</p>
</div>
</article>
/* Tailwind 4.3 */
.-mt-10 {
margin-top: calc(var(--spacing) * -10);
}
/* Con valor arbitrario */
.-mt-\[3px\] {
margin-top: calc(3px * -1);
}
/* -mt-px no usa la escala */
.-mt-px {
margin-top: -1px;
}
El colapso de márgenes
Es la causa de muchas sorpresas. Cuando dos márgenes verticales se tocan en un flujo de bloques, no se suman: se queda el mayor. Dentro de un contenedor flex o grid eso no ocurre.
<!-- En flujo de bloques -->
<div class="w-1/2 bg-gray-100">
<div class="mb-8 bg-indigo-200 p-2 font-mono text-sm">mb-8</div>
<div class="mt-4 bg-orange-200 p-2 font-mono text-sm">mt-4</div>
</div>
<!-- En un contenedor flex en columna -->
<div class="flex w-1/2 flex-col bg-gray-100">
<div class="mb-8 bg-indigo-200 p-2 font-mono text-sm">mb-8</div>
<div class="mt-4 bg-orange-200 p-2 font-mono text-sm">mt-4</div>
</div>

Figura 8. Margen negativo, y a la derecha el colapso: la separación del caso en bloque es menor que la del caso flex.
Separación medida: 32 px en el caso de bloque (gana mb-8), y 48 px en el flex (se suman).
El margen de un hijo también puede atravesar a su padre si este no tiene relleno ni borde, y entonces parece que el padre no contiene al hijo. Lo verás en la sección 7 al probar space-y-*: el margen del último hijo desaparece de la caja del padre si nada lo detiene.
💡 Consejo: Una regla que evita casi todos estos problemas: separa los hermanos desde el padre con
gap-*, y no mezclesmb-*ymt-*en elementos contiguos. Si un componente necesita separarse del exterior, deja esa decisión a quien lo coloca, no al propio componente.
6. Separación entre hijos: gap-*
gap es una propiedad CSS de flex y grid. Pone espacio solo entre los hijos, nunca antes del primero ni después del último, y no depende de cuántos hijos haya ni de si alguno está oculto.
| Clase | CSS |
|---|---|
gap-4 | gap |
gap-x-4 | column-gap (entre columnas) |
gap-y-4 | row-gap (entre filas) |
gap-px | gap: 1px |
<div class="flex gap-4 bg-gray-100">...</div>
<div class="grid grid-cols-3 gap-4 bg-gray-100">...</div>
<div class="grid grid-cols-3 gap-x-8 gap-y-2 bg-gray-100">...</div>

Figura 9. gap-4 en flex y en grid, y separación distinta por eje con gap-x-8 gap-y-2.
⚠️ Importante:
gap-*necesita que el padre seaflexogrid(también funciona en columnas múltiples). En un<div>normal no hace nada y no da ningún error: es un fallo frecuente.
7. space-*: la opción antigua 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) el selector es
> :not([hidden]) ~ :not([hidden])y pone el margen delante de cada hijo salvo el primero; en 4.x es:where(> :not(:last-child))y lo pone detrás. Si usas la LTS: las clases son las mismas, pero el hijo oculto que da problemas es el primero, y los márgenes propios del hijo no se pueden cambiar. Lo más seguro en ambas versiones es usarflexygap-*.
space-x-* y space-y-* nacieron antes de que gap funcionase bien en flex en todos los navegadores. La clase se pone en el padre, pero lo que hace es dar margen a los hijos:
/* Tailwind 3.4 */
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
/* Tailwind 4.3 */
:where(.space-y-4 > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
}
Hay dos cambios importantes:
- Qué hijo se salta. En 3.4 se salta el primero y en 4.x el último. Si ese hijo no se muestra, el espacio sobrante aparece en un sitio distinto.
- Especificidad.
:where()vale cero, así que en 4.x una clase en el hijo, comomt-10, le gana. En 3.4 el selector tiene especificidad de tres clases y le gana a cualquier utilidad del hijo.
Lo hemos comprobado con la misma página compilada en las dos versiones. Cada lista lleva un borde para que el margen no atraviese al padre:
<!-- A: último oculto -->
<ul class="space-y-4 border-2 border-orange-400 bg-orange-100">
<li class="bg-indigo-200 p-2">Uno</li>
<li class="bg-indigo-200 p-2">Dos</li>
<li class="hidden bg-indigo-200 p-2">Tres</li>
</ul>
<!-- B: primero oculto -->
<ul class="space-y-4 border-2 border-orange-400 bg-orange-100">
<li class="hidden bg-indigo-200 p-2">Uno</li>
<li class="bg-indigo-200 p-2">Dos</li>
<li class="bg-indigo-200 p-2">Tres</li>
</ul>
<!-- C: un hijo con su propio margen -->
<ul class="space-y-4 border-2 border-orange-400 bg-orange-100">
<li class="bg-indigo-200 p-2">Uno</li>
<li class="mt-10 bg-indigo-200 p-2">Dos</li>
<li class="bg-indigo-200 p-2">Tres</li>
</ul>
<!-- D: la alternativa con gap -->
<ul class="flex flex-col gap-4 border-2 border-orange-400 bg-orange-100">
<li class="bg-indigo-200 p-2">Uno</li>
<li class="bg-indigo-200 p-2">Dos</li>
<li class="hidden bg-indigo-200 p-2">Tres</li>
</ul>

Figura 10. La misma página en las dos versiones. El caso con gap-4 es el único que se ve igual.
| Lista | Altura en 3.4 | Altura en 4.3 | Qué ocurre |
|---|---|---|---|
| A: último oculto | 84 px | 100 px | En 4.3 sobran 16 px abajo |
| B: primero oculto | 100 px | 84 px | En 3.4 sobran 16 px arriba |
C: hijo con mt-10 | 132 px | 156 px | En 3.4 el mt-10 se ignora |
D: gap-4 | 84 px | 84 px | Correcto en las dos |
Otra debilidad de space-* es el salto de línea. Al basarse en márgenes, no sabe nada de filas, y con flex-wrap deja la segunda línea sin separación vertical y descuadrada:
<div class="flex w-64 flex-wrap space-x-4 bg-orange-200">...</div>
<div class="flex w-64 flex-wrap gap-x-4 gap-y-2 bg-orange-200">...</div>

Figura 11. Arriba, space-x-4 con flex-wrap. Abajo, gap-x-4 gap-y-2.
Valores negativos e inversión
space-* tiene dos cosas que gap-* no tiene. Admite negativos (-space-x-3), útiles para pilas de avatares solapados, ya que gap no puede ser negativo. Y tiene space-x-reverse y space-y-reverse, para listas con flex-row-reverse o flex-col-reverse.
Cuál elegir
| Situación | Elige | Motivo |
|---|---|---|
Hijos de flex o grid | gap-* | Sin efectos secundarios |
| Filas que pueden saltar de línea | gap-x-* y gap-y-* | space-* no entiende de filas |
| Texto en flujo normal (párrafos seguidos) | space-y-* | No obliga a cambiar el display |
| Hijos solapados | -space-x-* o -ms-* | gap no admite negativos |
| Un solo elemento con separación especial | mt-* | Es una excepción local |
La guía oficial de actualización a 4.0 recomienda pasar a flex con gap-* si el cambio de selectores te da problemas. Es la opción por defecto en este curso.
8. Anchura: w-*
La familia w-* tiene cinco tipos de valor: la escala, fracciones, nombres, palabras clave y unidades del viewport. Las dos primeras son de uso diario.
| Tipo | Ejemplos | CSS |
|---|---|---|
| Escala | w-64 | calc(var(--spacing) * 64) |
| Fracción | w-1/2, w-5/12 | calc(1 / 2 * 100%) |
| Porcentaje completo | w-full | 100% |
| Nombre | w-md | var(--container-md) |
| Palabra clave | w-auto, w-min, w-max, w-fit | auto, min-content… |
| Viewport | w-screen, w-dvw, w-svw, w-lvw | 100vw, 100dvw… |
| Un píxel | w-px | 1px |
Fracciones
Una fracción es un porcentaje del padre. En 3.4 se escribe el resultado (width: 50%, width: 41.666667%), y en 4.x se calcula en el propio CSS (calc(5 / 12 * 100%)).
<div class="w-full bg-indigo-200 p-2">w-full</div>
<div class="w-3/4 bg-indigo-200 p-2">w-3/4</div>
<div class="w-2/3 bg-indigo-200 p-2">w-2/3</div>
<div class="w-1/2 bg-indigo-200 p-2">w-1/2</div>
<div class="w-1/3 bg-indigo-200 p-2">w-1/3</div>
<div class="flex gap-2">
<div class="w-1/3 bg-orange-200 p-2">w-1/3</div>
<div class="w-2/3 bg-orange-200 p-2">w-2/3</div>
</div>
<div class="flex gap-2">
<div class="w-[calc(100%/3-0.25rem)] bg-orange-200 p-2">w-[...]</div>
<div class="flex-1 bg-orange-200 p-2">flex-1</div>
</div>

Figura 12. Arriba, fracciones del padre. Abajo, la serie de anchos con nombre.
⚠️ Importante:
w-1/3+w-2/3suman el 100 % del padre, y elgapañade 8 px más. En un contenedor flex los hijos encogen un poco para que quepa (es lo que ves en la figura), pero conshrink-0se saldrían. Para columnas con separación es mejorflex-1o una rejilla congrid-cols-*.
Fracciones libres 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) solo hay denominadores 2, 3, 4, 5, 6 y 12, y
w-7/13no genera CSS. Si usas la LTS: escribe un valor arbitrario comow-[53.846%]ow-[calc(7/13*100%)].
En 4.x vale cualquier fracción de enteros, como w-7/13.
Anchos con nombre 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4), donde esos nombres solo funcionan con
max-w-*. Si usas la LTS: convierte a la escala (w-80paraw-xs,w-96paraw-sm) o usa un valor arbitrario comow-[28rem]paraw-md.
En 4.x, w-*, min-w-* y max-w-* leen los nombres del espacio --container-* del tema. Solo hay versión para anchos: h-md no existe.
| Nombre | rem | px | Nombre | rem | px |
|---|---|---|---|---|---|
3xs | 16 | 256 | 2xl | 42 | 672 |
2xs | 18 | 288 | 3xl | 48 | 768 |
xs | 20 | 320 | 4xl | 56 | 896 |
sm | 24 | 384 | 5xl | 64 | 1024 |
md | 28 | 448 | 6xl | 72 | 1152 |
lg | 32 | 512 | 7xl | 80 | 1280 |
xl | 36 | 576 |
Las dos primeras filas son también novedad de 4.0, incluso para max-w-*: en 3.4 no existen max-w-3xs ni max-w-2xs.
Palabras clave: el ancho según el contenido
<div class="w-auto bg-indigo-200 p-2"><code>w-auto</code> ocupa todo el ancho disponible</div>
<div class="w-min bg-indigo-200 p-2"><code>w-min</code> se estrecha hasta la palabra más larga</div>
<div class="w-max bg-indigo-200 p-2"><code>w-max</code> no parte nunca el texto en líneas</div>
<div class="w-fit bg-indigo-200 p-2"><code>w-fit</code> se ajusta a lo que necesita</div>
<div class="w-fit bg-orange-200 p-2"><code>w-fit</code> pero si el texto es tan largo que no cabe en una sola línea, se limita al ancho disponible</div>
<div class="w-max bg-orange-200 p-2"><code>w-max</code> pero si el texto es tan largo que no cabe en una línea, sigue en una línea y se sale</div>

Figura 13. Las dos cajas naranjas muestran la diferencia entre w-fit y w-max: la primera respeta al padre y la segunda no.
| Clase | Valor | Comportamiento |
|---|---|---|
w-auto | auto | Valor por defecto. Un bloque ocupa todo el ancho |
w-min | min-content | Lo mínimo sin partir palabras |
w-max | max-content | Todo en una línea, aunque se salga |
w-fit | fit-content | Lo que necesite, sin pasar del disponible |
w-fit es la más útil de las cuatro. Sirve, por ejemplo, para que un bloque se ajuste a su texto y se pueda centrar con mx-auto.
w-screen y la barra de desplazamiento
w-screen es 100vw, y 100vw incluye la barra de desplazamiento vertical si esta ocupa espacio. En Windows, con barras clásicas, un elemento w-screen dentro de una página larga provoca un desplazamiento horizontal de unos pocos píxeles. Casi siempre lo que quieres es w-full, porque el padre ya excluye esa barra.
9. Altura: h-*
h-* comparte los valores de w-*, salvo los nombres:
| Tipo | Ejemplos |
|---|---|
| Escala | h-16, h-96 |
| Fracciones | h-1/2 |
| Palabras clave | h-auto, h-full, h-min, h-max, h-fit |
| Viewport | h-screen, h-dvh, h-svh, h-lvh |
| Un píxel | h-px |
La gran diferencia entre anchura y altura es que el flujo normal ocupa todo el ancho, pero solo el alto que necesita. Por eso los porcentajes de altura se comportan distinto.
Por qué h-full a veces no hace nada
Un porcentaje de altura se calcula sobre la altura del padre. Si el padre tiene height: auto, su altura depende de sus hijos, y el navegador no puede resolver el porcentaje: lo trata como auto.
<div class="w-1/2 bg-gray-100">
<div class="h-full bg-orange-200 p-2 font-mono text-sm">h-full, sin altura en el padre</div>
</div>
<div class="h-32 w-1/2 bg-gray-100">
<div class="h-full bg-indigo-200 p-2 font-mono text-sm">h-full, padre con h-32</div>
</div>

Figura 14. A la izquierda, h-full se queda en 36 px. A la derecha, llena los 128 px del padre.
Para llenar el alto del padre sin definir su altura, usa flex o grid: el valor por defecto stretch estira a los hijos sin ningún porcentaje. Lo verás en Flexbox.
h-lh, min-h-lh y max-h-lh: una línea de texto 🆕 v4.2
🆕 Desde Tailwind 4.2. No existe en la LTS (Tailwind 3.4). Si usas la LTS: escribe un valor arbitrario como
h-[1lh], que genera lo mismo.
lh es una unidad CSS que vale una altura de línea del elemento. Sirve para reservar sitio en líneas de texto, o para que un adorno mida lo mismo que el texto:
<p class="text-lg"><span class="inline-block h-lh w-1 bg-indigo-600 align-top"></span> Texto normal</p>
<p class="text-3xl"><span class="inline-block h-lh w-1 bg-indigo-600 align-top"></span> Texto grande</p>
La barra mide 28 px junto al text-lg y 36 px junto al text-3xl, sin que tengas que calcular nada. Con min-h-lh un párrafo vacío conserva la altura de una línea y la maquetación no salta cuando llega el texto.
⚠️ Importante: No confundas la fecha: comprobado instalando las versiones,
h-lhfalta en 4.1.0 y aparece en 4.2.0. Tampoco existew-lh.
10. El viewport en móviles: vh, svh, lvh y dvh
En un ordenador, la ventana mide lo que mide. En un móvil, la barra de direcciones aparece y desaparece al desplazarte, de modo que el viewport tiene dos alturas. La antigua unidad vh no se ajusta a esto: en navegadores móviles equivale a la altura grande, con la barra oculta. Por eso una sección h-screen se corta al cargar la página, y su botón inferior queda fuera de la vista.
CSS añadió tres unidades para resolverlo:
| Clase | Valor | Significado |
|---|---|---|
h-screen | 100vh | Clásica. En móvil, como lvh |
h-svh | 100svh | Small: altura con la barra visible, la más pequeña |
h-lvh | 100lvh | Large: altura con la barra oculta, la más grande |
h-dvh | 100dvh | Dynamic: la altura real en cada momento |
Figura 15. svh y lvh son fijas. dvh cambia cuando la barra aparece o desaparece.
Existen las mismas variantes para min-h-*, max-h-* y para anchos (w-dvw, w-svw, w-lvw), y todas funcionan en 3.4.
| Quieres | Usa | Por qué |
|---|---|---|
| Portada o sección a pantalla completa | min-h-svh | Se ve entera al cargar y no salta al desplazarte |
| Página con pie al fondo | min-h-dvh | Si hay poco contenido, llena lo visible |
| Panel de aplicación con desplazamiento interno | h-dvh | Se ajusta a la zona visible real |
| Modal a pantalla completa | h-dvh | Sus botones siempre quedan a la vista |
💡 Consejo:
dvhpuede provocar pequeños saltos, porque el navegador recalcula el tamaño mientras se oculta la barra. Si un elemento contiene mucho contenido debajo y notas el salto, pruebasvh.
Un error que parece buena idea
Con unidades nuevas, en CSS normal es habitual escribir dos declaraciones para que un navegador antiguo use la primera. Con clases eso no funciona:
<!-- No sirve: h-screen siempre gana -->
<div class="h-screen h-dvh">...</div>
La hoja la ordena Tailwind, y lo que queda en ambas versiones, medido en el navegador, es este orden:
.h-dvh .h-screen .min-h-dvh .min-h-screen
h-screen queda detrás de h-dvh y por tanto gana. Si de verdad necesitas compatibilidad con navegadores que no conocen dvh (solo puede ocurrir en 3.4, porque los navegadores que exige 4.x ya la entienden), usa una variante condicional:
<div class="h-screen supports-[height:100dvh]:h-dvh">...</div>
@supports (height:100dvh) {
.supports-\[height\:100dvh\]\:h-dvh {
height: 100dvh;
}
}
Dos patrones de página
El pie al fondo: una columna de al menos la altura de la pantalla, cuyo main se queda con lo que sobre. Si el contenido crece, la página crece.
<body class="flex min-h-dvh flex-col bg-gray-50 text-gray-900">
<header class="flex h-16 items-center bg-indigo-700 px-6 font-bold text-white">EMPRESA</header>
<main class="mx-auto w-full max-w-3xl flex-1 px-6 py-8">
<h1 class="text-2xl font-bold">Tu carrito está vacío</h1>
<p class="mt-2 text-gray-600">Vuelve al catálogo para añadir productos.</p>
</main>
<footer class="bg-gray-200 px-6 py-4 text-sm text-gray-700">© EMPRESA. Pie siempre abajo aunque haya poco contenido.</footer>
</body>
El panel de aplicación: altura fija a la pantalla, y solo se desplaza la zona central. La solución completa está en el ejercicio 10.

Figura 16. Los dos patrones de altura completa. El panel se desplaza por dentro: la página en sí no tiene barra de desplazamiento.
11. Tamaño a la vez: size-*
Cuando un elemento es cuadrado, size-* evita repetir w-* y h-*:
.size-16 {
width: calc(var(--spacing) * 16);
height: calc(var(--spacing) * 16);
}
Admite la escala (size-16), size-px, fracciones (size-1/2), size-full, size-auto, size-min, size-max y size-fit. Es lo natural para iconos y avatares.
<div class="size-6 rounded-full bg-indigo-600"></div>
<div class="size-10 rounded-full bg-indigo-600"></div>
<div class="size-16 rounded-full bg-indigo-600"></div>
<div class="size-24 rounded-full bg-orange-400"></div>
<div class="flex size-24 items-center justify-center rounded-lg bg-indigo-100">
<div class="size-1/2 rounded bg-indigo-600"></div>
</div>

Figura 17. Avatares con size-6 a size-24, y un size-1/2 dentro de un size-24.
Si combinas size-* con h-* o w-*, la clase más concreta gana, sin importar el orden: size-8 h-4 da 32 × 16 px en las dos versiones, y w-full size-8 conserva el w-full.
size-* llegó en la versión 3.4, así que puedes usarlo en la LTS.
size-dvh y compañía 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4), ni tampoco
w-dvhoh-dvw. Si usas la LTS: escribesize-[100dvh],w-[100dvh]oh-[100dvw].
4.x añade size-dvh, size-dvw, size-svh, size-svw, y las combinaciones cruzadas w-dvh (anchura igual a la altura del viewport) y h-dvw. No hay size-screen.
12. Límites: min-* y max-*
Las clases min-w-*, max-w-*, min-h-* y max-h-* ponen límites sin fijar un tamaño. Esto permite que el elemento siga siendo flexible. Es la forma adaptable de decir “ancho total, pero sin pasarse”:
<main class="mx-auto w-full max-w-3xl px-6">...</main>
En un móvil de 400 px ocupa los 400 px. En un monitor de 1920 px se queda en 768 px y se centra. No hace falta ninguna variante responsive.
| Familia | Escala | Palabras clave | Nombres |
|---|---|---|---|
max-w-* | Sí | none, full, min, max, fit, prose, screen-sm… | xs a 7xl (3xs, 2xs desde 4.0) |
min-w-* | Sí | 0, full, min, max, fit | Solo desde 4.0 |
max-h-* | Sí | none, full, min, max, fit, screen, dvh, svh, lvh | No |
min-h-* | Sí | 0, full, min, max, fit, screen, dvh, svh, lvh | No |
Quién gana: width, max-width o min-width
CSS aplica siempre el mismo orden: primero width, luego lo limita max-width, y min-width tiene la última palabra. No depende de Tailwind. Comprobado con w-40 max-w-24 min-w-32: mide 128 px en las dos versiones, que es lo que pide min-w-32.
max-w-prose y la longitud de línea
max-w-prose es max-width: 65ch: unos 65 caracteres por línea, una longitud cómoda de lectura. Va ligada al tamaño de letra del propio elemento.
<p class="max-w-prose bg-indigo-100 p-2 text-sm">...</p>
max-w-screen-*
Funciona en las dos versiones y limita a un punto de ruptura, con una diferencia de unidad: max-w-screen-md es 768px en 3.4 y var(--breakpoint-md) (48 rem) en 4.x. La documentación de 4.x prefiere la forma max-w-(--breakpoint-md), que verás en la sección 14.
El misterio de min-w-0
Es uno de los errores más repetidos con flex. Un hijo flex tiene min-width: auto, lo que le impide encoger por debajo del ancho de su contenido. Si dentro hay un truncate (que no parte el texto), el contenido mide todo lo que mide la frase, y el hijo no encoge.
<!-- Bien: el propio hijo tiene truncate, que incluye overflow: hidden -->
<div class="flex gap-2 bg-gray-100 p-2 outline outline-gray-400">
<div class="size-8 shrink-0 bg-indigo-600"></div>
<p class="truncate">Portátil de 16 pulgadas con gráfica dedicada para juegos y edición de vídeo</p>
</div>
<!-- Mal: un envoltorio sin límite -->
<div class="flex gap-2 bg-gray-100 p-2 outline outline-gray-400">
<div class="size-8 shrink-0 bg-orange-500"></div>
<div>
<p class="truncate">Portátil de 16 pulgadas con gráfica dedicada para juegos y edición de vídeo</p>
</div>
</div>
<!-- Bien: min-w-0 permite encoger al envoltorio -->
<div class="flex gap-2 bg-gray-100 p-2 outline outline-gray-400">
<div class="size-8 shrink-0 bg-indigo-600"></div>
<div class="min-w-0">
<p class="truncate">Portátil de 16 pulgadas con gráfica dedicada para juegos y edición de vídeo</p>
</div>
</div>

Figura 18. Arriba, max-w-prose frente a un texto sin límite. Abajo, la segunda fila desborda porque le falta min-w-0: su contenido mide 515 px en un contenedor de 368 px.
El mismo problema ocurre en vertical: un hijo de una columna flex tiene min-height: auto. El mínimo automático desaparece si el propio hijo lleva overflow-*, y por eso la primera fila de la figura funciona sin min-w-0: truncate incluye overflow: hidden. Pero si la zona con desplazamiento es un envoltorio más interno, el hijo flex necesita min-h-0. Medido en una columna de 160 px con un contenido de 384 px:
<!-- Funciona: main mide 160 px y se desplaza -->
<main class="flex-1 overflow-y-auto">...</main>
<!-- No funciona: main crece hasta 384 px y no hay desplazamiento -->
<main class="flex-1">
<div class="h-full overflow-y-auto">...</div>
</main>
<!-- Funciona: main mide 160 px, y se desplaza el envoltorio -->
<main class="min-h-0 flex-1">
<div class="h-full overflow-y-auto">...</div>
</main>
overflow-* se estudia en Layout y posicionamiento.
min-w-auto y min-h-auto 🆕 v4.0
🆕 Desde Tailwind 4.0. No existen en la LTS (Tailwind 3.4). Si usas la LTS: usa valores arbitrarios:
min-w-[auto]ymin-h-[auto].
Restablecen el valor por defecto de un hijo flex o grid. Solo los necesitarás para anular un min-w-0 en un punto de ruptura concreto.
13. Propiedades lógicas
Hasta ahora has usado casi siempre lados físicos: izquierda, derecha, arriba y abajo. Pero una interfaz en árabe o hebreo se lee de derecha a izquierda, y lo que en español es “antes del texto” allí está en el lado contrario. Las propiedades lógicas no hablan de lados de la pantalla, sino del sentido de la escritura:
- Eje inline: en el que avanzan las letras de una línea. En español, de izquierda a derecha.
- Eje block: en el que se apilan las líneas. En español, de arriba abajo.
- Inicio (start) y fin (end): dentro de cada eje.
Figura 19. El significado de s, e, bs y be depende de la escritura.
ps-*, pe-*, ms-* y me-*
Funcionan en las dos versiones y son las que más se usan:
<div dir="rtl">
<div class="border-l-4 border-orange-500 bg-white pl-4">pl-4</div>
<div class="border-s-4 border-indigo-600 bg-white ps-4">ps-4</div>
<div class="flex items-center bg-white p-2"><span class="size-6 bg-orange-500"></span><span class="ml-4">ml-4</span></div>
<div class="flex items-center bg-white p-2"><span class="size-6 bg-indigo-600"></span><span class="ms-4">ms-4</span></div>
<div class="flex space-x-4 bg-white p-2"><span class="bg-indigo-200 px-2">space-x-4</span><span class="bg-indigo-200 px-2">B</span><span class="bg-indigo-200 px-2">C</span></div>
</div>

Figura 20. En la columna rtl, las filas naranjas usan lados físicos y se quedan mal: la barra sigue a la izquierda y el texto se pega al cuadrado. Las de lados lógicos se adaptan solas.
Fíjate también en la última fila de la columna rtl. En 3.4, space-x-4 usa margin-left, y la separación entre “space-x-4” y “B” desaparece, salvo que añadas rtl:space-x-reverse (la variante rtl: se estudia en Variantes y estados). En 4.x usa margin-inline-end y funciona sin más.
💡 Consejo: Acostúmbrate a escribir
ms-*,me-*,ps-*ype-*desde el principio. En español se ven igual que los físicos, no cuestan nada más, y tu interfaz queda lista para cualquier idioma. Los ejemplos del resto del curso los usan cuando el lado depende del sentido del texto.
No mezcles físicas y lógicas sobre el mismo lado
Con ml-6 ms-2, las dos clases afectan al mismo margen en español. Quién gana depende del orden en la hoja, y ese orden es distinto en cada versión:
| Clases | Tailwind 3.4 | Tailwind 4.3 |
|---|---|---|
ml-6 ms-2 | margin-left: 8px (gana ms-2) | margin-left: 24px (gana ml-6) |
Una migración de versión podría cambiar el aspecto de tu página sin ningún aviso. Elige una u otra en cada elemento.
Escritura vertical: px-* cambia de sentido 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4)
px-*ypy-*son siempre horizontal y vertical en la pantalla. En 4.x siguen los ejes de la escritura. Si usas la LTS: si tu interfaz emplea escritura vertical, usa[padding-inline:2rem]y[padding-block:2rem]para obtener el comportamiento de 4.x.
Tailwind no trae ninguna clase para writing-mode, así que se escribe con una propiedad arbitraria. Es un caso poco común, útil para títulos laterales o texto en japonés o chino:
<div class="h-48 w-20 bg-indigo-400 px-8 [writing-mode:vertical-rl]">...</div>
<div class="h-48 w-20 bg-indigo-400 py-8 [writing-mode:vertical-rl]">...</div>
<div class="h-48 w-20 bg-indigo-400 ps-8 [writing-mode:vertical-rl]">...</div>
<div class="h-48 w-20 bg-indigo-400 pt-8 [writing-mode:vertical-rl]">...</div>

Figura 21. El azul es el relleno. Observa que px-8 y py-8 se intercambian entre versiones, mientras que ps-8 y pt-8 no cambian.
| Clase | Relleno en 3.4 | Relleno en 4.3 |
|---|---|---|
px-8 | Izquierda y derecha | Arriba y abajo |
py-8 | Arriba y abajo | Izquierda y derecha |
ps-8 | Arriba | Arriba |
pt-8 | Arriba | Arriba |
pbs-*, pbe-*, mbs-* y mbe-* 🆕 v4.2
🆕 Desde Tailwind 4.2. No existe en la LTS (Tailwind 3.4). Si usas la LTS: en escritura horizontal usa
pt-*,pb-*,mt-*ymb-*, que hacen lo mismo. Si necesitas la versión lógica, escribe[padding-block-start:1rem].
Completan los sufijos del eje block. Son la pareja de ps-*/pe-*, y admiten negativos y auto igual que el resto de márgenes (-mbs-2, mbs-auto).
| Clase | CSS |
|---|---|
pbs-4 | padding-block-start |
pbe-4 | padding-block-end |
mbs-4 | margin-block-start |
mbe-4 | margin-block-end |
inline-* y block-* 🆕 v4.2
🆕 Desde Tailwind 4.2. No existe en la LTS (Tailwind 3.4). Si usas la LTS: en escritura horizontal usa
w-*,h-*,min-w-*,max-w-*,min-h-*ymax-h-*. Si necesitas la versión lógica, escribe[inline-size:14rem].
Son la versión lógica de w-* y h-*. Admiten los mismos valores (escala, fracciones, full, auto, fit, dvw…):
| Clase | CSS | En español equivale a |
|---|---|---|
inline-56 | inline-size | w-56 |
block-32 | block-size | h-32 |
min-inline-4, max-inline-4 | min-inline-size, max-inline-size | min-w-4, max-w-4 |
min-block-32, max-block-32 | min-block-size, max-block-size | min-h-32, max-h-32 |
⚠️ Importante: No las confundas con
inlineyblock, las clases dedisplay.blocka secas esdisplay: block, yblock-4es una altura. También son distintasinline-block(display) einline-full(ancho completo). El nombre se presta a errores al leer el código.
El interés de estas clases se ve al cambiar de escritura. Mismas clases, distinta orientación:
<article class="inline-56 min-block-32 bg-indigo-100 pbs-4 pbe-8 ps-6 pe-2">
<h3 class="mbe-2 font-bold">Horizontal</h3>
<p>Portátil ligero de 14 pulgadas, 16 GB RAM</p>
</article>
<article class="inline-56 min-block-32 bg-orange-100 pbs-4 pbe-8 ps-6 pe-2 [writing-mode:vertical-rl]">
<h3 class="mbe-2 font-bold">Vertical</h3>
<p>Portátil ligero de 14 pulgadas, 16 GB RAM</p>
</article>

Figura 22. La tarjeta horizontal mide 224 × 128 px y la vertical, 128 × 224 px. El relleno se reparte siguiendo la escritura.
Medidas en el navegador. Relleno de la horizontal: arriba 16, abajo 32, izquierda 24, derecha 8. Relleno de la vertical: arriba 24, abajo 8, izquierda 32, derecha 16.
14. Valores a medida
La escala no lo cubre todo. Tienes tres vías, que ya presentó el módulo 02: arbitrarios, variables y tema.
Valores arbitrarios
Funcionan igual en las dos versiones:
| Clase | CSS |
|---|---|
w-[37px] | width: 37px |
m-[5%] | margin: 5% |
h-[50dvh] | height: 50dvh |
w-[calc(100%-2rem)] | width: calc(100% - 2rem) |
p-[10px_20px] | padding: 10px 20px |
gap-[2cqw] | gap: 2cqw |
El guion bajo equivale a un espacio. Dentro de calc() Tailwind añade los espacios que exige CSS alrededor de + y -.
Variables CSS 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) se escribe con corchetes,
w-[--ancho-lateral], y los paréntesis no generan nada. Si usas la LTS: sustituyew-(--ancho-lateral)porw-[--ancho-lateral].
<div class="w-(--ancho-lateral) bg-orange-200 p-2 [--ancho-lateral:18rem]">w-(--ancho-lateral)</div>
.w-\(--ancho-lateral\) {
width: var(--ancho-lateral);
}
Mide 288 px en el navegador. Recuerda del módulo 02 que p-[--relleno] en 4.x compila a padding: --relleno, que no es válido.
También con paréntesis puedes aprovechar los puntos de ruptura del tema: max-w-(--breakpoint-md).
Valores propios en el tema 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) la escala y los anchos se definen en
tailwind.config.js, contheme.extend.spacingytheme.extend.maxWidth. Si usas la LTS: define los mismos nombres ahí, como se muestra abajo.
Si un valor se repite y tiene un significado, dale nombre en vez de copiarlo como arbitrario:
@import "tailwindcss";
@theme {
--spacing-tarjeta: 1.75rem;
--container-ficha: 22rem;
}
<div class="max-w-ficha bg-indigo-200 p-tarjeta">max-w-ficha p-tarjeta</div>
En el navegador, el relleno es de 28 px y el ancho máximo de 352 px. El equivalente en 3.4:
// tailwind.config.js
export default {
content: ["./*.html"],
theme: {
extend: {
spacing: { tarjeta: "1.75rem" },
maxWidth: { ficha: "22rem" },
},
},
};
⚠️ Importante: Evita usar el mismo nombre en
--spacing-*y en--container-*. Lo descubrimos preparando este módulo: con--spacing-tarjeta: 1.75remy--container-tarjeta: 22rem, la clasemax-w-tarjetadiomax-width: 28px, porque--spacing-*tiene prioridad. No hay ningún aviso. El tema se estudia a fondo en Personalizar el tema.
15. Caso real: el catálogo de la tienda
Vamos a montar el catálogo de EMPRESA con lo que has aprendido. Fíjate en que no hay ni un solo valor arbitrario.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catálogo de EMPRESA</title>
<link rel="stylesheet" href="/app.css">
</head>
<body class="flex min-h-dvh flex-col bg-gray-50 text-gray-900">
<header class="bg-white shadow-sm">
<div class="mx-auto flex h-16 max-w-6xl items-center gap-6 px-4">
<span class="text-lg font-bold text-indigo-700">EMPRESA</span>
<nav class="ms-auto flex gap-6 text-sm">
<a href="#" class="hover:text-indigo-700">Catálogo</a>
<a href="#" class="hover:text-indigo-700">Ofertas</a>
<a href="#" class="hover:text-indigo-700">Contacto</a>
</nav>
<div class="flex size-9 items-center justify-center rounded-full bg-indigo-600 text-sm font-bold text-white">IT</div>
</div>
</header>
<main class="mx-auto w-full max-w-6xl flex-1 px-4 py-8">
<div class="max-w-prose">
<h1 class="text-3xl font-bold">Catálogo</h1>
<p class="mt-2 text-gray-600">Equipos y periféricos para tu oficina, con envío en 24 horas y garantía de tres años en todos los portátiles.</p>
</div>
<section class="mt-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<article class="flex flex-col rounded-lg bg-white p-6 shadow-sm">
<div class="h-32 w-full rounded bg-indigo-100"></div>
<p class="mt-4 text-xs font-semibold text-indigo-600 uppercase">Portátiles</p>
<h2 class="mt-1 text-lg font-bold">Portátil UltraBook 14</h2>
<p class="mt-2 text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<div class="mt-auto flex items-center justify-between pt-4">
<span class="text-xl font-bold">899 €</span>
<button class="rounded bg-indigo-600 px-4 py-2 text-sm text-white">Añadir</button>
</div>
</article>
<!-- Otras dos tarjetas iguales: Portátil Gaming 16 y Ratón inalámbrico -->
</section>
</main>
<footer class="mt-8 bg-gray-200 px-4 py-6 text-center text-sm text-gray-700">© EMPRESA</footer>
</body>
</html>

Figura 23. El catálogo a 1100 px y a 400 px de ancho.
Las decisiones de espaciado y tamaño, una a una:
| Clases | Dónde | Por qué |
|---|---|---|
flex min-h-dvh flex-col | <body> | Patrón de pie al fondo |
max-w-6xl mx-auto px-4 | Cabecera y main | El contenido se alinea en la misma columna. px-4 evita que se pegue al borde en móvil |
w-full | main | Hijo de un flex en columna con márgenes auto: sin w-full se encogería a su contenido |
flex-1 | main | Se queda con la altura sobrante y empuja el pie |
h-16 | Cabecera | Altura fija, sin depender del tamaño de letra |
ms-auto | <nav> | Empuja los enlaces y el avatar al final de la fila; funcionaría también en RTL |
size-9 | Avatar | Es cuadrado |
max-w-prose | Introducción | Líneas cómodas de leer |
gap-6 | Rejilla | Separación uniforme sin márgenes en las tarjetas |
p-6 | Tarjeta | Relleno uniforme |
mt-1, mt-2, mt-4 | Dentro de la tarjeta | Lo relacionado se acerca: 4 px entre categoría y nombre, 16 px hasta la imagen |
mt-auto | Fila de precio | Se pega abajo aunque las descripciones tengan distinta longitud |
📘 Concepto: La proximidad comunica. Los elementos relacionados deben estar más cerca entre sí que de los demás. Los valores
mt-1,mt-2ymt-4de la tarjeta forman una jerarquía: es la idea que guía cualquier decisión de espaciado.
⚠️ Importante: El detalle de
w-fullenmaines un error muy común.mx-autoen un hijo de flex anula el estiramiento, y elmainpasa a medir lo que mida su contenido. Si tu contenedor central sale estrecho, revisa esto.
16. Errores frecuentes y depuración
| Síntoma | Causa probable | Solución |
|---|---|---|
mx-auto no centra | Sin ancho, o elemento en línea | Añade w-*/max-w-*, o block |
gap-4 no hace nada | El padre no es flex ni grid | Añade flex o grid |
h-full no llena | Padre con altura auto | Dale altura al padre o usa flex/grid |
| Barra horizontal en toda la página | w-screen, o algo con w-max | Usa w-full; busca lo que se sale |
Texto truncate desborda en flex | Falta min-w-0 | Añade min-w-0 en el hijo flex |
Zona con overflow-y-auto no se desplaza | Está dentro de un hijo flex sin min-h-0 | Añade min-h-0 a ese hijo |
| El pie se corta en el móvil | h-screen | Usa h-dvh o min-h-dvh |
| Hay menos espacio del esperado | Colapso de márgenes | Usa gap-* |
| Espacio de más al final de una lista | space-y-* con el último hijo oculto | Usa gap-* |
Mi mt-* no hace nada en un hijo | space-y-* en 3.4 | Usa gap-*, o cambia el margen del padre |
p-13 o w-md no aplica | Clase de 4.x en 3.4 | Consulta las cajas de este módulo |
-p-4 no aplica | No existe relleno negativo | Usa un margen negativo en el hijo |
size-4 no aplica a un <span> | Elemento en línea | Añade inline-block o block |
max-w-tarjeta da un valor absurdo | Nombre repetido en --spacing-* | Cambia el nombre en el tema |
Para localizar lo que causa un desbordamiento horizontal, pega esto en la consola del navegador:
const ancho = document.documentElement.clientWidth;
for (const elemento of document.querySelectorAll("*")) {
if (elemento.getBoundingClientRect().right > ancho) console.log(elemento);
}
Muestra los elementos cuyo borde derecho pasa del ancho visible. Para repasar el JavaScript usado, tienes el Curso de JavaScript.
17. Ejercicios
Los ejercicios se hacen en tu proyecto de Vite. Cada uno admite una página propia. Los resueltos se han compilado y comprobado en el navegador.
Ejercicio 1: Predice el resultado
Sin ejecutar nada, responde:
- ¿Cuántos píxeles mide
p-5? ¿Y-mt-px? - ¿Qué anchura final tiene un
<div>conw-40 max-w-24 min-w-32? - Un
<div>conw-48 p-6 border-8y otro igual conbox-content: ¿cuánto mide cada uno de ancho? - Dos bloques seguidos, el primero con
mb-8y el segundo conmt-4: ¿qué separación queda? ¿Y si el padre esflex flex-col?
Ver solución
p-5son 5 × 4 = 20 px.-mt-pxes -1 px:pxno usa la escala.- 128 px.
max-w-24reduce los 160 px a 96, ymin-w-32tiene la última palabra y sube a 128. - 192 px con
box-border, que es el valor de Preflight, y 192 + 48 + 16 = 256 px conbox-content. - 32 px en flujo de bloques, porque los márgenes colapsan y gana el mayor. En
flex flex-colson 48 px, porque se suman.
Los cuatro resultados son los que se midieron en las secciones 2, 5 y 12.
Ejercicio 2: Alerta de stock
Crea una alerta con fondo ámbar, ancho completo de su contenedor, 24 px de relleno lateral y 16 px vertical. Debe tener un título y un texto separados 4 px: “Stock bajo” y “Quedan 4 unidades de la Torre Workstation.”.
Ver solución
<div class="rounded-lg bg-amber-100 px-6 py-4">
<h2 class="font-bold">Stock bajo</h2>
<p class="mt-1 text-sm">Quedan 4 unidades de la Torre Workstation.</p>
</div>
No hace falta w-full: un <div> ya ocupa todo el ancho. px-6 son 24 px, py-4 son 16 px y mt-1 son 4 px.
Ejercicio 3: Bloque centrado y adaptable
Crea una tarjeta “Condiciones de envío” centrada, que no supere los 448 px, y que en un móvil estrecho no toque los bordes de la pantalla.
Ver solución
<section class="bg-gray-100 px-4 py-8">
<article class="mx-auto max-w-md rounded-lg bg-white p-6">
<h2 class="font-bold">Condiciones de envío</h2>
<p class="mt-2 text-sm">Los pedidos se envían en 24 horas.</p>
</article>
</section>
max-w-md limita a 448 px sin fijar el ancho, y mx-auto centra. El px-4 va en el padre: si lo pusieras como margen en la tarjeta, chocaría con mx-auto, porque ambas clases escriben el mismo margen.
Ejercicio 4: Barra de navegación
Crea una barra con la marca EMPRESA y los enlaces Catálogo y Ofertas a la izquierda, y el enlace Entrar pegado a la derecha. Debe funcionar también en una interfaz RTL y no usar ningún elemento vacío.
Ver solución
<nav class="flex items-center gap-4 bg-gray-100 px-4 py-2">
<span class="font-bold">EMPRESA</span>
<a href="#">Catálogo</a>
<a href="#">Ofertas</a>
<a href="#" class="ms-auto">Entrar</a>
</nav>
ms-auto se queda con todo el espacio libre antes de “Entrar”. Con ml-auto el diseño se rompería en RTL, porque el espacio libre quedaría en el lado equivocado.
Ejercicio 5: Sustituye space-*
Este menú muestra un espacio de más cuando se ocultan categorías con JavaScript. Corrígelo.
<ul class="space-y-2 text-sm">
<li class="bg-gray-100 p-2">Portátiles</li>
<li class="hidden bg-gray-100 p-2">Sobremesa</li>
<li class="bg-gray-100 p-2">Monitores</li>
<li class="hidden bg-gray-100 p-2">Periféricos</li>
</ul>
Ver solución
<ul class="flex flex-col gap-2 text-sm">
<li class="bg-gray-100 p-2">Portátiles</li>
<li class="hidden bg-gray-100 p-2">Sobremesa</li>
<li class="bg-gray-100 p-2">Monitores</li>
<li class="hidden bg-gray-100 p-2">Periféricos</li>
</ul>
En 4.x, space-y-2 pone el margen en todos los hijos menos en el último del HTML, que aquí es uno oculto. Por eso “Monitores” lleva 8 px de más. gap-2 solo cuenta los elementos que se muestran.
Ejercicio 6: Pila de avatares
Crea cuatro avatares circulares de 48 px solapados 12 px, con un borde blanco para separarlos.
Ver solución
<div class="flex -space-x-3">
<div class="size-12 rounded-full border-2 border-white bg-indigo-600"></div>
<div class="size-12 rounded-full border-2 border-white bg-orange-500"></div>
<div class="size-12 rounded-full border-2 border-white bg-teal-500"></div>
<div class="size-12 rounded-full border-2 border-white bg-gray-400"></div>
</div>
Aquí space-* sí tiene sentido: gap no puede ser negativo. -space-x-3 son -12 px.
Ejercicio 7: Pie al fondo
Crea una página con cabecera de 64 px, contenido centrado que no supere 768 px, y un pie que quede abajo aunque haya poco contenido. La solución debe funcionar en móviles.
Ver solución
Es el patrón de la sección 10:
<body class="flex min-h-dvh flex-col bg-gray-50 text-gray-900">
<header class="flex h-16 items-center bg-indigo-700 px-6 font-bold text-white">EMPRESA</header>
<main class="mx-auto w-full max-w-3xl flex-1 px-6 py-8">
<h1 class="text-2xl font-bold">Tu carrito está vacío</h1>
<p class="mt-2 text-gray-600">Vuelve al catálogo para añadir productos.</p>
</main>
<footer class="bg-gray-200 px-6 py-4 text-sm text-gray-700">© EMPRESA. Pie siempre abajo aunque haya poco contenido.</footer>
</body>
min-h-dvh y no h-dvh: con mucho contenido la página debe poder crecer. min-h-screen también sería válido, pero en móvil obliga a desplazar la página para ver el pie.
Ejercicio 8: Fila de empleado en RTL
Crea una fila con avatar, nombre y un botón “Ver” al final. Debe llevar una barra de color al inicio del texto. Comprueba que se adapta sola al poner dir="rtl".
Ver solución
<section dir="rtl">
<div class="flex items-center gap-3 border-s-4 border-indigo-600 bg-gray-100 py-2 ps-4 pe-2">
<div class="size-10 rounded-full bg-indigo-600"></div>
<p class="text-sm">Isabel Torres</p>
<button class="ms-auto rounded bg-indigo-600 px-3 py-1 text-sm text-white">Ver</button>
</div>
</section>
Todo es lógico: border-s-4, ps-4, pe-2 y ms-auto. border-s-* pertenece a Bordes, sombras y efectos. py-2 y px-3 pueden quedar así porque son simétricas.
Ejercicio 9: Traduce a Tailwind 3.4
Un compañero necesita compilar en 3.4 este fragmento. Escribe el equivalente de cada clase.
p-13 w-md h-lh pbs-4 inline-56 size-dvh w-(--ancho) max-w-3xs w-7/13
Ver solución
| Tailwind 4.3 | Tailwind 3.4 |
|---|---|
p-13 | p-[3.25rem] |
w-md | w-[28rem] |
h-lh | h-[1lh] |
pbs-4 | pt-4 en escritura horizontal |
inline-56 | w-56 en escritura horizontal |
size-dvh | size-[100dvh] |
w-(--ancho) | w-[--ancho] |
max-w-3xs | max-w-[16rem] |
w-7/13 | w-[calc(7/13*100%)] |
Todas se comprobaron compilándolas en 3.4.19. Lo que no te avisa es el compilador: una clase de 4.x en 3.4 simplemente no genera CSS, y el elemento se queda sin estilo.
Ejercicio 10: Panel de aplicación
Crea un panel con una barra lateral fija de 256 px, una cabecera de 56 px y una lista de 30 pedidos, de modo que solo se desplace la lista y la página no tenga barra de desplazamiento propia.
Ver solución
<body class="bg-gray-50 text-gray-900">
<div class="flex h-dvh">
<aside class="w-64 shrink-0 space-y-2 bg-indigo-900 p-4 text-white">
<p class="mb-4 font-bold">Panel EMPRESA</p>
<a href="#" class="block rounded bg-indigo-700 px-3 py-2">Pedidos</a>
<a href="#" class="block rounded px-3 py-2">Clientes</a>
</aside>
<div class="flex min-w-0 flex-1 flex-col">
<header class="flex h-14 shrink-0 items-center bg-white px-6 font-bold shadow-sm">Pedidos</header>
<main class="flex-1 overflow-y-auto p-6">
<ul class="space-y-3" id="lista"></ul>
</main>
</div>
</div>
<script>
const lista = document.getElementById("lista");
for (let i = 0; i < 30; i++) {
const li = document.createElement("li");
li.className = "rounded bg-white p-4 shadow-sm";
li.textContent = `Pedido ${5001 + i}`;
lista.append(li);
}
</script>
</body>
Medido en una ventana de 700 px: el documento mide 700 px y no se desplaza; main mide 644 px y su contenido, 2076 px.
h-dvhfija la altura total.shrink-0impide que la barra lateral y la cabecera encojan.min-w-0permite que la columna central sea más estrecha que su contenido.overflow-y-auto(se estudia en Layout y posicionamiento) añade el desplazamiento y, además, permite quemainencoja por debajo de su contenido. Por eso aquí no hace faltamin-h-0. Haría falta si el desplazamiento estuviese en un envoltorio dentro demain, como viste en la sección 12.
Ejercicio 11 (propuesto): Ficha de producto
Maqueta la ficha del Portátil Gaming 16: imagen a la izquierda y datos a la derecha, con un ancho total limitado a max-w-4xl. Usa gap-* para separar las columnas y max-w-prose para la descripción. Debe verse bien a 400 px y a 1100 px de ancho.
Ejercicio 12 (propuesto): Modo compacto
Toma la tabla o lista que prefieras y añade un modo compacto con [--spacing:0.2rem] en el contenedor. Compruébalo en la tarjeta del catálogo y decide qué valores conviene fijar para que no cambien de tamaño. ¿Cómo lo harías en Tailwind 3.4?
Resumen
| Concepto | API | Idea clave |
|---|---|---|
| Modelo de caja | box-border, box-content | Con Preflight, w-* incluye relleno y borde |
| Escala | --spacing, número × 4 px | Multiplicadores libres desde 4.0; lista cerrada en 3.4 |
| Relleno | p-*, px-*, pt-*, ps-* | Está dentro de la caja y lleva el fondo. No admite negativos |
| Margen | m-*, mx-auto, -mt-* | Está fuera; admite auto y negativos; colapsa en bloques |
px, py, mx, my | padding-inline, padding-block | Lógicas desde 4.0; solo se nota en escritura vertical |
| Separación | gap-*, gap-x-*, gap-y-* | Requiere flex o grid; la mejor opción |
| Separación antigua | space-x-*, space-y-* | Da margen a los hijos; selector distinto en 3.4 y 4.x |
| Anchura | w-64, w-1/2, w-full, w-fit | Fracción del padre; w-fit respeta al padre, w-max no |
| Anchos con nombre | w-md, max-w-md | w-md desde 4.0; max-w-md en ambas versiones |
| Altura | h-16, h-full, h-lh | h-full necesita altura en el padre; h-lh desde 4.2 |
| Viewport | h-svh, h-lvh, h-dvh, min-h-dvh | En móvil, vh es la altura grande |
| Cuadrados | size-10, size-1/2 | Equivale a w-* + h-*; desde 3.4 |
| Límites | min-*, max-*, max-w-prose | min gana a max, y max gana a width |
| Desbordes en flex | min-w-0, min-h-0 | El mínimo automático impide encoger |
| Lógicas | ps-*, pe-*, ms-*, me-* | Se adaptan a RTL; no las mezcles con físicas |
| Lógicas de bloque | pbs-*, mbs-*, inline-*, block-* | Desde 4.2; en 3.4, usa las físicas |
| A medida | w-[37px], w-(--ancho), --spacing-*, --container-* | Corchetes en ambas; paréntesis desde 4.0; no repitas nombres en el tema |