Módulo 09: CSS Grid
Objetivos del módulo
- Decidir con criterio cuándo usar Grid y cuándo Flexbox
- Manejar el vocabulario de Grid: líneas, pistas, celdas, áreas y huecos
- Crear rejillas con
grid-cols-*ygrid-rows-*, y entender por qué generanminmax(0, 1fr) - Superar las 12 columnas con los valores dinámicos de v4 y conocer los límites de v3
- Colocar elementos con
col-span-*,col-start-*,col-end-*y sus equivalentes de fila, incluidas las líneas negativas - Distinguir la rejilla explícita de la implícita y controlar esta con
auto-rows-*yauto-cols-* - Cambiar el flujo de colocación con
grid-flow-*y saber qué riesgos tienedense - Escribir pistas a medida con valores arbitrarios:
fr,minmax(),repeat(),auto-fillyauto-fit - Crear catálogos que se adaptan solos, sin puntos de ruptura
- Dibujar páginas con áreas y líneas con nombre mediante valores arbitrarios
- Alinear con
justify-items-*,justify-self-*y la familiaplace-* - Superponer elementos sin
position: absolute - Alinear contenido entre tarjetas y filas con subgrid
- Montar layouts completos: página, panel de control y mosaico
- Detectar los problemas de accesibilidad del orden visual y depurar una rejilla
1. Preparación del laboratorio
Este módulo usa el proyecto Vite del módulo 01. Basta con un index.html que enlace app.css, y un app.css con esta línea:
@import "tailwindcss";
Si cada ejemplo va en su propia página, recuerda que @tailwindcss/vite no detecta los HTML creados con el servidor en marcha: guarda app.css para que vuelva a escanear.
Los ejemplos envuelven el contenido en <body class="p-4 text-gray-900">, salvo los layouts de página completa. Usan datos de la tienda EMPRESA y algunas clases que se estudian en otros módulos:
| Clases | Qué hacen | Dónde se estudian |
|---|---|---|
p-*, m-*, h-*, w-*, size-*, min-h-dvh | Espaciado y tamaño | Espaciado y tamaño |
text-*, font-*, uppercase | Texto | Tipografía |
bg-*, border-*, rounded-*, shadow | Color y bordes | Color y fondos, Bordes y efectos |
overflow-*, aspect-*, z-*, contents | Layout | Layout y posicionamiento |
items-*, self-*, justify-*, content-*, flex | Alineación y Flexbox | Flexbox |
sm:, md:, lg: | Aplican una clase a partir de un ancho de pantalla | Diseño responsive |
hover: | Aplica una clase al pasar el ratón | Variantes y estados |
Por ahora te basta con saber que md:grid-cols-4 significa “cuatro columnas a partir de 768 px de ancho”. El módulo siguiente lo estudia a fondo. Si necesitas repasar las propiedades de CSS Grid, tienes el Curso de HTML y CSS.
2. Grid o Flexbox
Flexbox trabaja en una dimensión: coloca elementos en una línea y, si no caben, abre otra. Cada línea se reparte por su cuenta y no sabe nada de las demás. Grid trabaja en dos dimensiones: primero define filas y columnas, y luego coloca los elementos dentro.
La diferencia se resume en quién manda. En Flexbox manda el contenido: el tamaño de los elementos decide cómo se forman las líneas. En Grid manda el contenedor: las columnas existen aunque no haya nada dentro.
<p class="mb-2 text-sm font-semibold text-gray-600">Flexbox: cada fila se reparte por su cuenta</p>
<div class="flex flex-wrap gap-2">
<div class="grow basis-48 rounded bg-amber-100 p-3 text-center">Portátiles</div>
<div class="grow basis-48 rounded bg-amber-100 p-3 text-center">Monitores</div>
<div class="grow basis-48 rounded bg-amber-100 p-3 text-center">Telefonía</div>
<div class="grow basis-48 rounded bg-amber-100 p-3 text-center">Audio</div>
<div class="grow basis-48 rounded bg-amber-100 p-3 text-center">Mobiliario</div>
</div>
<p class="mt-6 mb-2 text-sm font-semibold text-gray-600">Grid: las columnas existen antes que el contenido</p>
<div class="grid grid-cols-3 gap-2">
<div class="rounded bg-indigo-100 p-3 text-center">Portátiles</div>
<div class="rounded bg-indigo-100 p-3 text-center">Monitores</div>
<div class="rounded bg-indigo-100 p-3 text-center">Telefonía</div>
<div class="rounded bg-indigo-100 p-3 text-center">Audio</div>
<div class="rounded bg-indigo-100 p-3 text-center">Mobiliario</div>
</div>

Figura 1. Con flex-wrap y grow, los dos últimos elementos se estiran y dejan de alinearse con los de arriba. Con Grid siguen en sus columnas.
Criterios prácticos:
| Situación | Elige | Por qué |
|---|---|---|
| Barra de navegación, grupo de botones, etiquetas | Flexbox | Una dimensión, y cada elemento mide lo que necesita |
| Icono junto a un texto, centrar un elemento | Flexbox o Grid | Las dos soluciones son igual de cortas |
| Catálogo, galería, calendario | Grid | Las columnas deben alinearse entre filas |
| Estructura de página con cabecera, lateral y pie | Grid | Dos dimensiones a la vez |
| Formulario con etiquetas y campos alineados | Grid | Cada columna se ajusta a su contenido más ancho |
| Elementos superpuestos | Grid | Varios elementos pueden compartir celda |
No son rivales. Lo normal es un Grid para la estructura de la página y Flexbox dentro de cada componente.
💡 Consejo: Una pregunta rápida: ¿quiero que los elementos de una fila se alineen con los de la fila de abajo? Si la respuesta es sí, necesitas Grid.
3. El vocabulario de Grid
Las clases de Tailwind son traducciones casi directas de CSS, así que conviene dominar los términos. El error más común al empezar es confundir línea con columna.
Figura 2. Anatomía de una rejilla de tres columnas y dos filas.
| Término | Inglés | Qué es |
|---|---|---|
| Línea | line | Separación numerada entre pistas. Es a lo que apuntan col-start-* y col-end-* |
| Pista | track | Una fila o una columna entera |
| Celda | cell | Intersección de una fila y una columna |
| Área | area | Rectángulo formado por una o más celdas |
| Hueco | gap | Separación entre pistas |
Dos ideas de esta tabla se usan en todo el módulo:
- Con N columnas hay N + 1 líneas. Con
grid-cols-3, las líneas verticales van de la 1 a la 4. Por eso una rejilla de 12 columnas tienecol-end-13. - Las líneas también se cuentan desde el final, con números negativos: la línea -1 es la última. Así puedes decir “hasta el final” sin saber cuántas columnas hay.
4. Crear la rejilla
grid e inline-grid
grid genera display: grid. El contenedor se comporta como un bloque por fuera y como una rejilla por dentro. inline-grid genera display: inline-grid: por dentro es igual, pero por fuera se integra en la línea de texto y mide lo que su contenido.
<p class="mb-2 text-sm font-semibold text-gray-600">grid: ocupa toda la línea</p>
<p>
Stock
<span class="grid grid-cols-2 gap-1 text-center text-sm">
<span class="rounded bg-indigo-100 px-2">Madrid</span>
<span class="rounded bg-indigo-100 px-2">Valencia</span>
</span>
por almacén
</p>
<p class="mt-6 mb-2 text-sm font-semibold text-gray-600">inline-grid: se integra en el texto</p>
<p>
Stock
<span class="inline-grid grid-cols-2 gap-1 text-center text-sm">
<span class="rounded bg-indigo-100 px-2">Madrid</span>
<span class="rounded bg-indigo-100 px-2">Valencia</span>
</span>
por almacén
</p>

Figura 3. grid ocupa toda la línea. inline-grid se queda dentro de la frase.
Fíjate en que grid por sí solo apenas cambia nada: sin columnas, los hijos se apilan como bloques en una única columna. Lo que da forma a la rejilla son las clases siguientes.
grid-cols-*
grid-cols-3 crea tres columnas iguales:
<div class="grid grid-cols-3 gap-3">
<div class="rounded bg-indigo-100 p-3 text-center">1</div>
<div class="rounded bg-indigo-100 p-3 text-center">2</div>
<div class="rounded bg-indigo-100 p-3 text-center">3</div>
<div class="rounded bg-indigo-100 p-3 text-center">4</div>
<div class="rounded bg-indigo-100 p-3 text-center">5</div>
<div class="rounded bg-indigo-100 p-3 text-center">6</div>
<div class="rounded bg-indigo-100 p-3 text-center">7</div>
</div>

Figura 4. grid-cols-3: los elementos se colocan solos, por orden, y abren filas nuevas según hacen falta.
No has tenido que decir nada a los hijos. Grid los coloca automáticamente: de izquierda a derecha y, cuando se acaba la fila, en la siguiente. La clase genera este CSS:
.grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
Con esta página de 600 px de ancho, el navegador calcula tres columnas de 181.328px. Para llegar a esa cifra resta los dos huecos de 12 px y el relleno de la página, y reparte lo que queda.
grid-cols-none genera grid-template-columns: none y sirve para quitar una rejilla a partir de cierto ancho.
Por qué minmax(0, 1fr) y no 1fr
Lo lógico sería escribir repeat(3, 1fr). Tailwind no lo hace, y el motivo evita uno de los errores más frecuentes de Grid.
fr es una fracción del espacio libre. El problema es que una pista 1fr tiene un mínimo implícito: auto, es decir, no puede ser más estrecha que el contenido mínimo que lleva dentro. Si dentro hay algo que no se puede partir, como una tabla o un bloque <pre>, la columna crece, descuadra el reparto y empuja al resto fuera del contenedor.
minmax(0, 1fr) quita ese mínimo: la pista puede encoger hasta cero, así que el reparto se respeta siempre. Compara las dos versiones:
<p class="mb-1 text-sm font-semibold text-red-700">grid-cols-[1fr_1fr]: la columna crece con el contenido</p>
<div id="roto" class="grid grid-cols-[1fr_1fr] gap-3 rounded border-2 border-red-400 p-2">
<div class="rounded bg-gray-100 p-2">
<h3 class="font-semibold">Registro de pedidos</h3>
<pre class="overflow-x-auto text-sm">GET /api/pedidos/5001/lineas?incluir=producto,cliente&orden=descendente 200 12ms</pre>
</div>
<div class="rounded bg-indigo-100 p-2">Resumen</div>
</div>
<p class="mt-5 mb-1 text-sm font-semibold text-green-700">grid-cols-2: minmax(0, 1fr) respeta el reparto</p>
<div id="bien" class="grid grid-cols-2 gap-3 rounded border-2 border-green-500 p-2">
<div class="rounded bg-gray-100 p-2">
<h3 class="font-semibold">Registro de pedidos</h3>
<pre class="overflow-x-auto text-sm">GET /api/pedidos/5001/lineas?incluir=producto,cliente&orden=descendente 200 12ms</pre>
</div>
<div class="rounded bg-indigo-100 p-2">Resumen</div>
</div>

Figura 5. Con 1fr, la línea larga del <pre> ensancha la columna. Con minmax(0, 1fr), el reparto se mantiene y el <pre> se desplaza por dentro.
Estos son los valores reales medidos en el navegador, con un contenedor de 604 px:
| Rejilla | Columnas calculadas | Ancho del contenido (scrollWidth) |
|---|---|---|
grid-cols-[1fr_1fr] | 631.781px 80.5156px | 732 px: se desborda |
grid-cols-2 | 288px 288px | 604 px: cabe |
Fíjate en que el <pre> ya llevaba overflow-x-auto, y aun así la columna creció. Ese detalle confunde mucho: overflow-x-auto limita al propio <pre> si algo limita su ancho, pero la pista mide el contenido mínimo de sus hijos antes de que eso ocurra.
Las grid-cols-* numéricas ya te protegen. El problema reaparece cuando escribes tú las pistas, como verás en la sección 9. Hay dos soluciones equivalentes:
<!-- Opción 1: quitar el mínimo en la pista -->
<div class="grid grid-cols-[14rem_minmax(0,1fr)]">...</div>
<!-- Opción 2: quitar el mínimo en el hijo -->
<div class="grid grid-cols-[1fr_1fr]">
<div class="min-w-0">...</div>
<div>...</div>
</div>
He aquí la segunda opción aplicada al ejemplo anterior. Al medirla, las columnas vuelven a 288px 288px y el scrollWidth a 604 px:
<div id="arreglo" class="grid grid-cols-[1fr_1fr] gap-3 rounded border-2 border-green-500 p-2">
<div class="min-w-0 rounded bg-gray-100 p-2">
<h3 class="font-semibold">Registro de pedidos</h3>
<pre class="overflow-x-auto text-sm">GET /api/pedidos/5001/lineas?incluir=producto,cliente&orden=descendente 200 12ms</pre>
</div>
<div class="rounded bg-indigo-100 p-2">Resumen</div>
</div>
📘 Concepto: Todo hijo de una rejilla tiene
min-width: auto, que en la práctica significa “no me hagas más estrecho que mi contenido”.min-w-0cambia ese valor. Es el mismo mecanismo que estudiaste con los elementos flexibles en el módulo 08.
gap-*, gap-x-* y gap-y-*
Los huecos separan pistas, nunca el borde del contenedor. Funcionan igual que en Flexbox (módulo 03), con la escala de espaciado. Puedes dar valores distintos a cada eje:
<div class="grid grid-cols-4 gap-x-8 gap-y-2">
<div class="rounded bg-indigo-100 p-3 text-center">1</div>
<div class="rounded bg-indigo-100 p-3 text-center">2</div>
<div class="rounded bg-indigo-100 p-3 text-center">3</div>
<div class="rounded bg-indigo-100 p-3 text-center">4</div>
<div class="rounded bg-indigo-100 p-3 text-center">5</div>
<div class="rounded bg-indigo-100 p-3 text-center">6</div>
<div class="rounded bg-indigo-100 p-3 text-center">7</div>
<div class="rounded bg-indigo-100 p-3 text-center">8</div>
</div>

Figura 6. gap-x-8 gap-y-2: 32 px entre columnas y 8 px entre filas.
| Clase | Propiedad |
|---|---|
gap-4 | gap |
gap-x-4 | column-gap |
gap-y-4 | row-gap |
Valores dinámicos 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
grid-cols-*ygrid-rows-*solo llegan a 12, ygrid-cols-15no genera nada, sin ningún aviso. Si usas la LTS: escribe la pista completa congrid-cols-[repeat(15,minmax(0,1fr))], o añade el valor agridTemplateColumnsentailwind.config.js.
En v4 las clases numéricas no salen de una lista: el compilador acepta cualquier entero y genera el CSS que corresponde.
<p class="mb-1 text-sm font-semibold text-gray-600">grid-cols-15 (solo v4)</p>
<div class="grid grid-cols-15 gap-1 text-center text-sm">
<div class="rounded bg-indigo-100 py-2">1</div><div class="rounded bg-indigo-100 py-2">2</div>
<div class="rounded bg-indigo-100 py-2">3</div><div class="rounded bg-indigo-100 py-2">4</div>
<div class="rounded bg-indigo-100 py-2">5</div><div class="rounded bg-indigo-100 py-2">6</div>
<div class="rounded bg-indigo-100 py-2">7</div><div class="rounded bg-indigo-100 py-2">8</div>
<div class="rounded bg-indigo-100 py-2">9</div><div class="rounded bg-indigo-100 py-2">10</div>
<div class="rounded bg-indigo-100 py-2">11</div><div class="rounded bg-indigo-100 py-2">12</div>
<div class="rounded bg-indigo-100 py-2">13</div><div class="rounded bg-indigo-100 py-2">14</div>
<div class="rounded bg-indigo-100 py-2">15</div>
</div>
<p class="mt-5 mb-1 text-sm font-semibold text-gray-600">grid-cols-[repeat(15,minmax(0,1fr))] (v3 y v4)</p>
<div class="grid grid-cols-[repeat(15,minmax(0,1fr))] gap-1 text-center text-sm">
<div class="rounded bg-amber-100 py-2">1</div><div class="rounded bg-amber-100 py-2">2</div>
<div class="rounded bg-amber-100 py-2">3</div><div class="rounded bg-amber-100 py-2">4</div>
<div class="rounded bg-amber-100 py-2">5</div><div class="rounded bg-amber-100 py-2">6</div>
<div class="rounded bg-amber-100 py-2">7</div><div class="rounded bg-amber-100 py-2">8</div>
<div class="rounded bg-amber-100 py-2">9</div><div class="rounded bg-amber-100 py-2">10</div>
<div class="rounded bg-amber-100 py-2">11</div><div class="rounded bg-amber-100 py-2">12</div>
<div class="rounded bg-amber-100 py-2">13</div><div class="rounded bg-amber-100 py-2">14</div>
<div class="rounded bg-amber-100 py-2">15</div>
</div>

Figura 7. Las dos rejillas dan el mismo resultado en v4. En v3, la primera no tiene columnas.
Esta tabla recoge los límites comprobados con las dos versiones instaladas:
| Familia | Máximo en 3.4 | En 4.x | Equivalente en 3.4 por encima del máximo |
|---|---|---|---|
grid-cols-*, grid-rows-* | 12 | Sin límite | grid-cols-[repeat(15,minmax(0,1fr))] |
col-span-*, row-span-* | 12 | Sin límite | col-[span_13/span_13] |
col-start-*, col-end-* | 13 | Sin límite | col-start-[14] |
row-start-*, row-end-* | 13 | Sin límite | row-start-[14] |
grid-cols-0 no existe en ninguna de las dos versiones.
5. Filas: grid-rows-*
grid-rows-3 genera grid-template-rows: repeat(3, minmax(0, 1fr)). Es la clase espejo de grid-cols-3, con un matiz: las columnas tienen casi siempre un ancho conocido, pero la altura de un contenedor suele depender de lo que tiene dentro.
<div class="grid grid-cols-2 gap-4">
<div>
<p class="mb-1 text-sm font-semibold text-gray-600">grid-rows-3 con h-56</p>
<div class="grid h-56 grid-rows-3 gap-2 rounded border border-gray-300 p-2">
<div class="rounded bg-indigo-100 p-1 text-center">Ventas</div>
<div class="rounded bg-indigo-100 p-1 text-center">Compras</div>
<div class="rounded bg-indigo-100 p-1 text-center">Stock</div>
</div>
</div>
<div>
<p class="mb-1 text-sm font-semibold text-gray-600">grid-rows-3 sin altura</p>
<div class="grid grid-rows-3 gap-2 rounded border border-gray-300 p-2">
<div class="rounded bg-indigo-100 p-1 text-center">Ventas</div>
<div class="rounded bg-indigo-100 p-1 text-center">Compras</div>
<div class="rounded bg-indigo-100 p-1 text-center">Stock<br>con dos líneas</div>
</div>
</div>
</div>

Figura 8. Con altura fija, las filas reparten el espacio. Sin altura, todas igualan a la fila más alta.
Estos son los valores medidos:
| Rejilla | Filas calculadas | Qué ocurre |
|---|---|---|
Con h-56 | 63.3281px cada una | Reparten la altura disponible |
| Sin altura | 56px cada una | No hay espacio que repartir, así que todas igualan a la más alta |
El segundo caso es útil: grid-rows-3 sin altura es una forma sencilla de conseguir tres filas iguales, sea cual sea el contenido.
La mayoría de las veces no necesitarás grid-rows-*. Si solo declaras columnas, las filas se crean solas con la altura de su contenido, que suele ser lo que quieres. Declara filas en dos casos: cuando el diseño tenga una estructura vertical fija, como una página completa, o cuando el flujo vaya por columnas (lo verás en la sección 8). grid-rows-none quita la definición.
6. Colocar elementos
Hasta aquí Grid colocaba los elementos por su cuenta. Ahora vas a decidir tú dónde va cada uno. Hay dos formas de pensarlo: decir cuántas pistas ocupa o decir entre qué líneas va.
Ocupar varias pistas: col-span-* y row-span-*
<div class="grid grid-cols-4 gap-3">
<div class="col-span-2 rounded bg-indigo-200 p-3 text-center">col-span-2</div>
<div class="row-span-2 rounded bg-amber-200 p-3 text-center">row-span-2</div>
<div class="rounded bg-gray-100 p-3 text-center">normal</div>
<div class="rounded bg-gray-100 p-3 text-center">normal</div>
<div class="rounded bg-gray-100 p-3 text-center">normal</div>
<div class="rounded bg-gray-100 p-3 text-center">normal</div>
<div class="col-span-full rounded bg-green-200 p-3 text-center">col-span-full</div>
</div>

Figura 9. Los elementos con span empujan a los demás, que siguen colocándose en orden en lo que queda libre.
| Clase | CSS |
|---|---|
col-span-2 | grid-column: span 2 / span 2 |
row-span-2 | grid-row: span 2 / span 2 |
col-span-full | grid-column: 1 / -1 |
row-span-full | grid-row: 1 / -1 |
col-auto | grid-column: auto |
row-auto | grid-row: auto |
col-auto y row-auto deshacen una colocación anterior. Son útiles con variantes: col-span-2 md:col-auto.
Elegir las líneas: col-start-* y col-end-*
col-start-2 significa “empieza en la línea 2”, y col-end-5 significa “termina en la línea 5”. Como la línea 5 cierra la cuarta columna, ese elemento ocupa las columnas 2, 3 y 4.
Puedes combinar un extremo con un span, y usar líneas negativas para contar desde el final:
<div class="grid grid-cols-6 gap-2">
<div class="col-start-2 col-end-5 rounded bg-indigo-200 p-3 text-center">col-start-2 col-end-5</div>
<div class="col-span-2 col-start-3 rounded bg-amber-200 p-3 text-center">col-span-2 col-start-3</div>
<div class="col-span-3 col-end-7 rounded bg-green-200 p-3 text-center">col-span-3 col-end-7</div>
<div class="col-start-2 -col-end-1 rounded bg-pink-200 p-3 text-center">col-start-2 -col-end-1</div>
<div class="-col-start-3 -col-end-1 rounded bg-sky-200 p-3 text-center">-col-start-3 -col-end-1</div>
</div>

Figura 10. Cinco formas de elegir posición en una rejilla de seis columnas.
| Clase | CSS |
|---|---|
col-start-2 | grid-column-start: 2 |
col-end-5 | grid-column-end: 5 |
-col-end-1 | grid-column-end: calc(1 * -1) |
col-start-auto | grid-column-start: auto |
row-start-2 | grid-row-start: 2 |
row-end-4 | grid-row-end: 4 |
Combinar col-span-2 con col-start-3 funciona, aunque las dos clases afectan a la misma colocación: Tailwind escribe col-span-* antes que col-start-* en la hoja, tanto en v3 como en v4. Primero se aplica el atajo grid-column: span 2 / span 2 y después grid-column-start: 3 sustituye solo el inicio. El resultado es 3 / span 2. El orden de las clases en tu HTML no influye, como viste en el módulo 02.
⚠️ Importante: Una línea que no existe no da error: el navegador la crea.
col-start-9en una rejilla degrid-cols-6añade columnas implícitas y el elemento acaba donde no esperabas.
La trampa de col-span-full
col-span-full significa “de la línea 1 a la línea -1”. Parece equivalente a “todo el ancho”, pero la línea -1 es la última de la rejilla explícita, la que has declarado. Si no has declarado columnas, la línea -1 es la misma que la 1, y el elemento ocupa una sola columna:
<p class="mb-1 text-sm font-semibold text-red-700">Sin columnas explícitas: col-span-full ocupa una sola</p>
<div class="grid auto-cols-fr gap-2">
<div class="col-start-1 rounded bg-indigo-100 p-2 text-center">A</div>
<div class="col-start-2 rounded bg-indigo-100 p-2 text-center">B</div>
<div class="col-start-3 rounded bg-indigo-100 p-2 text-center">C</div>
<div class="col-span-full row-start-2 rounded bg-red-200 p-2 text-center">col-span-full</div>
</div>
<p class="mt-5 mb-1 text-sm font-semibold text-green-700">Con grid-cols-3: llega de la línea 1 a la -1</p>
<div class="grid grid-cols-3 gap-2">
<div class="rounded bg-indigo-100 p-2 text-center">A</div>
<div class="rounded bg-indigo-100 p-2 text-center">B</div>
<div class="rounded bg-indigo-100 p-2 text-center">C</div>
<div class="col-span-full rounded bg-green-200 p-2 text-center">col-span-full</div>
</div>

Figura 11. Sin columnas explícitas no hay línea -1 útil. Con grid-cols-3, col-span-full llega hasta el final.
Lo mismo pasa con row-span-full: casi nunca declaras filas, por lo que esta clase rara vez hace lo que parece. La sección siguiente explica qué son las pistas implícitas.
El atajo col-* y row-* 🆕 v4.1
🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: usa
col-start-2yrow-start-3, que colocan el elemento en la misma pista.
col-2 genera grid-column: 2, y row-3 genera grid-row: 3. Son una forma corta de decir “en esta columna y esta fila”:
<div class="grid grid-cols-4 gap-2">
<div class="col-2 row-1 rounded bg-indigo-200 p-3 text-center">col-2 row-1</div>
<div class="col-4 row-2 rounded bg-amber-200 p-3 text-center">col-4 row-2</div>
<div class="col-1 row-3 rounded bg-green-200 p-3 text-center">col-1 row-3</div>
</div>

Figura 12. Colocar con col-* y row-* deja libres las demás celdas.
⚠️ Importante: No combines
col-3concol-span-2. Las dos generan la propiedadgrid-column, así que una anula a la otra. Tailwind escribecol-3antes en la hoja, por lo que ganacol-span-2y la posición se pierde. Para indicar inicio y tamaño, usacol-start-3 col-span-2.
7. La rejilla implícita
Si colocas ocho elementos en una rejilla de grid-cols-3 grid-rows-2, que tiene seis celdas, los dos que sobran no desaparecen. El navegador crea una fila más. Las pistas que declaras forman la rejilla explícita, y las que crea el navegador forman la implícita.
Figura 13. Las pistas declaradas y las que añade el navegador.
Las pistas implícitas no reciben su tamaño de grid-cols-* ni de grid-rows-*. Por defecto miden lo que pida su contenido, y eso se nota:
<p class="mb-1 text-sm font-semibold text-gray-600">Sin auto-rows: la tercera fila mide lo que su contenido</p>
<div class="grid h-52 grid-cols-3 grid-rows-2 gap-2 rounded border border-gray-300 p-2">
<div class="rounded bg-indigo-200 p-1 text-center">1</div>
<div class="rounded bg-indigo-200 p-1 text-center">2</div>
<div class="rounded bg-indigo-200 p-1 text-center">3</div>
<div class="rounded bg-indigo-200 p-1 text-center">4</div>
<div class="rounded bg-indigo-200 p-1 text-center">5</div>
<div class="rounded bg-indigo-200 p-1 text-center">6</div>
<div class="rounded bg-amber-200 p-1 text-center">7 implícita</div>
<div class="rounded bg-amber-200 p-1 text-center">8 implícita</div>
</div>
<p class="mt-5 mb-1 text-sm font-semibold text-gray-600">auto-rows-[5rem]: todas las filas nuevas miden 80 px</p>
<div class="grid auto-rows-[5rem] grid-cols-3 gap-2 rounded border border-gray-300 p-2">
<div class="rounded bg-indigo-200 p-1 text-center">1</div>
<div class="rounded bg-indigo-200 p-1 text-center">2</div>
<div class="rounded bg-indigo-200 p-1 text-center">3</div>
<div class="rounded bg-amber-200 p-1 text-center">4 implícita</div>
<div class="rounded bg-amber-200 p-1 text-center">5 implícita</div>
</div>

Figura 14. Arriba, la fila implícita es más baja que las explícitas. Abajo, auto-rows-[5rem] fija el tamaño de todas las filas nuevas.
Medido en el navegador: la primera rejilla da 71px 71px 32px, y la segunda 80px 80px.
auto-rows-* y auto-cols-*
Estas clases dan tamaño a las pistas implícitas:
| Clase | CSS |
|---|---|
auto-rows-auto | grid-auto-rows: auto |
auto-rows-min | grid-auto-rows: min-content |
auto-rows-max | grid-auto-rows: max-content |
auto-rows-fr | grid-auto-rows: minmax(0, 1fr) |
auto-rows-[10rem] | grid-auto-rows: 10rem |
auto-rows-[minmax(8rem,auto)] | grid-auto-rows: minmax(8rem,auto) |
auto-cols-* tiene los mismos valores y actúa sobre grid-auto-columns.
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<p class="mb-1 font-semibold text-gray-600">auto-rows-auto</p>
<div class="grid auto-rows-auto gap-2">
<div class="rounded bg-indigo-100 p-2">Una línea</div>
<div class="rounded bg-indigo-100 p-2">Esta tarjeta tiene bastante más texto y necesita varias líneas para caber</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">auto-rows-fr</p>
<div class="grid auto-rows-fr gap-2">
<div class="rounded bg-indigo-100 p-2">Una línea</div>
<div class="rounded bg-indigo-100 p-2">Esta tarjeta tiene bastante más texto y necesita varias líneas para caber</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">auto-rows-min con h-56</p>
<div class="grid h-56 auto-rows-min gap-2 rounded border border-gray-300 p-1">
<div class="rounded bg-indigo-100 p-2">Una línea</div>
<div class="rounded bg-indigo-100 p-2">Esta tarjeta tiene bastante más texto y necesita varias líneas para caber</div>
</div>
</div>
</div>

Figura 15. auto-rows-fr iguala todas las filas. auto-rows-min las pega arriba y deja el resto libre.
El patrón auto-rows-[minmax(8rem,auto)] merece recordarse: las filas miden al menos 8rem y crecen si el contenido lo pide. Evita tanto las tarjetas demasiado bajas como el texto cortado.
8. El flujo de colocación: grid-flow-*
El algoritmo de colocación automática sigue una regla: recorre la rejilla por filas y pone cada elemento en el primer sitio libre que encuentra a partir de la posición del anterior. grid-flow-* cambia esa regla.
| Clase | CSS | Comportamiento |
|---|---|---|
grid-flow-row | grid-auto-flow: row | Por filas. Es el valor por defecto |
grid-flow-col | grid-auto-flow: column | Por columnas |
grid-flow-dense | grid-auto-flow: dense | Rellena huecos |
grid-flow-row-dense | grid-auto-flow: row dense | Por filas y rellenando |
grid-flow-col-dense | grid-auto-flow: column dense | Por columnas y rellenando |
Por filas o por columnas
<div class="grid grid-cols-2 gap-6">
<div>
<p class="mb-1 text-sm font-semibold text-gray-600">grid-cols-3 (grid-flow-row, por defecto)</p>
<div class="grid grid-cols-3 gap-2 text-center">
<div class="rounded bg-indigo-200 p-2">1</div>
<div class="rounded bg-indigo-200 p-2">2</div>
<div class="rounded bg-indigo-200 p-2">3</div>
<div class="rounded bg-indigo-200 p-2">4</div>
<div class="rounded bg-indigo-200 p-2">5</div>
<div class="rounded bg-indigo-200 p-2">6</div>
<div class="rounded bg-indigo-200 p-2">7</div>
</div>
</div>
<div>
<p class="mb-1 text-sm font-semibold text-gray-600">grid-flow-col grid-rows-3</p>
<div class="grid grid-flow-col grid-rows-3 gap-2 text-center">
<div class="rounded bg-amber-200 p-2">1</div>
<div class="rounded bg-amber-200 p-2">2</div>
<div class="rounded bg-amber-200 p-2">3</div>
<div class="rounded bg-amber-200 p-2">4</div>
<div class="rounded bg-amber-200 p-2">5</div>
<div class="rounded bg-amber-200 p-2">6</div>
<div class="rounded bg-amber-200 p-2">7</div>
</div>
</div>
</div>

Figura 16. Con grid-flow-col se llena la columna antes de pasar a la siguiente, y sobran columnas implícitas.
Fíjate en que grid-flow-col se combina con grid-rows-3 y no con grid-cols-*. Si el flujo va por columnas, lo que limita es la altura: declaras cuántas filas hay, y las columnas se crean según hagan falta.
Un carril horizontal
grid-flow-col y auto-cols-* forman un carril que crece hacia la derecha sin tener que declarar columnas:
<ul class="grid auto-cols-[12rem] grid-flow-col gap-3 overflow-x-auto pb-3">
<li class="rounded-lg border border-gray-200 p-3">
<p class="font-semibold">Portátil UltraBook 14</p>
<p class="text-indigo-700">899 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-3">
<p class="font-semibold">Monitor 27 4K</p>
<p class="text-indigo-700">329 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-3">
<p class="font-semibold">Teclado mecánico</p>
<p class="text-indigo-700">79,90 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-3">
<p class="font-semibold">Webcam HD</p>
<p class="text-indigo-700">45 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-3">
<p class="font-semibold">Disco SSD 1TB</p>
<p class="text-indigo-700">89,90 €</p>
</li>
</ul>

Figura 17. Un carril de productos. Cada columna implícita mide 12rem, y el contenedor se desplaza en horizontal.
Si le añades las clases snap-* del módulo 07, tienes un carrusel sin JavaScript.
Rellenar huecos con dense
Con el flujo normal, un elemento ancho que no cabe salta a la fila siguiente y deja un agujero, y los siguientes no vuelven atrás para rellenarlo. dense les permite volver:
<div class="grid grid-cols-2 gap-6">
<div>
<p class="mb-1 text-sm font-semibold text-gray-600">grid-flow-row: deja huecos</p>
<div class="grid grid-cols-3 gap-2 text-center">
<div class="rounded bg-gray-200 p-2">1</div>
<div class="col-span-2 rounded bg-indigo-200 p-2">2</div>
<div class="col-span-3 rounded bg-indigo-300 p-2">3</div>
<div class="col-span-2 rounded bg-indigo-200 p-2">4</div>
<div class="col-span-2 rounded bg-indigo-200 p-2">5</div>
<div class="rounded bg-amber-200 p-2">6</div>
<div class="rounded bg-amber-200 p-2">7</div>
</div>
</div>
<div>
<p class="mb-1 text-sm font-semibold text-gray-600">grid-flow-row-dense: rellena y reordena</p>
<div class="grid grid-flow-row-dense grid-cols-3 gap-2 text-center">
<div class="rounded bg-gray-200 p-2">1</div>
<div class="col-span-2 rounded bg-indigo-200 p-2">2</div>
<div class="col-span-3 rounded bg-indigo-300 p-2">3</div>
<div class="col-span-2 rounded bg-indigo-200 p-2">4</div>
<div class="col-span-2 rounded bg-indigo-200 p-2">5</div>
<div class="rounded bg-amber-200 p-2">6</div>
<div class="rounded bg-amber-200 p-2">7</div>
</div>
</div>
</div>

Figura 18. grid-flow-row-dense sube los elementos 6 y 7 a los huecos, aunque en el HTML van después del 5.
El resultado es más compacto, pero tiene un precio: el orden visual deja de coincidir con el del HTML. En la figura, el 6 aparece antes que el 5. Si esos elementos son enlaces o tarjetas interactivas, afecta a quien navega con teclado, como se explica en la sección 16.
💡 Consejo:
densees adecuado para galerías de imágenes, donde el orden da igual. Evítalo en listas con un orden con sentido, como resultados de búsqueda o productos ordenados por precio.
9. Pistas a medida con valores arbitrarios
Las clases numéricas hacen todas las pistas iguales. Si necesitas algo distinto, como un lateral fijo y un contenido flexible, escribe el valor entre corchetes. Los guiones bajos se convierten en espacios.
<div class="space-y-3 text-center text-sm">
<div class="grid grid-cols-[10rem_1fr_auto] gap-2">
<div class="rounded bg-indigo-200 p-2">10rem</div>
<div class="rounded bg-amber-200 p-2">1fr</div>
<div class="rounded bg-green-200 p-2">auto (lo que mida)</div>
</div>
<div class="grid grid-cols-[2fr_1fr] gap-2">
<div class="rounded bg-indigo-200 p-2">2fr</div>
<div class="rounded bg-amber-200 p-2">1fr</div>
</div>
<div class="grid grid-cols-[minmax(0,20rem)_1fr] gap-2">
<div class="rounded bg-indigo-200 p-2">minmax(0,20rem)</div>
<div class="rounded bg-amber-200 p-2">1fr</div>
</div>
<div class="grid grid-cols-[repeat(3,5rem)_1fr] gap-2">
<div class="rounded bg-indigo-200 p-2">5rem</div>
<div class="rounded bg-indigo-200 p-2">5rem</div>
<div class="rounded bg-indigo-200 p-2">5rem</div>
<div class="rounded bg-amber-200 p-2">1fr</div>
</div>
</div>

Figura 19. Cuatro formas de definir pistas distintas.
| Unidad | Significado | Ejemplo |
|---|---|---|
| Longitud | Tamaño fijo | grid-cols-[10rem_1fr] |
fr | Fracción del espacio libre | grid-cols-[2fr_1fr] |
auto | Lo que pida el contenido | grid-cols-[auto_1fr] |
minmax(a,b) | Entre un mínimo y un máximo | grid-cols-[minmax(0,20rem)_1fr] |
repeat(n,x) | Repetir una pista | grid-cols-[repeat(3,5rem)_1fr] |
min-content | El mínimo sin desbordar | grid-cols-[min-content_1fr] |
max-content | El ancho sin saltos de línea | grid-cols-[max-content_1fr] |
El reparto de fr se hace después de restar lo que ocupan las pistas fijas y los huecos. En grid-cols-[10rem_1fr_auto], primero se reservan 160 px y el ancho que pida la tercera columna, y 1fr se lleva todo lo demás.
⚠️ Importante: Con pistas a medida pierdes la protección de
grid-cols-*. Si la columna flexible puede contener tablas, código o texto sin espacios, escribegrid-cols-[14rem_minmax(0,1fr)]. Así lo hace el panel de la sección 15.
auto-fill y auto-fit: columnas que se adaptan solas
repeat() admite una cuenta automática: “mete todas las columnas de este tamaño que quepan”. Eso permite una rejilla adaptable sin puntos de ruptura.
La diferencia entre las dos palabras clave solo se nota cuando sobra espacio:
auto-fillcrea las columnas que quepan, aunque se queden vacías.auto-fitcrea las mismas y luego colapsa a 0 las vacías, de modo que1frreparte el espacio entre las ocupadas.
<p class="mb-1 text-sm font-semibold text-gray-600">auto-fill: conserva las columnas vacías</p>
<div class="grid grid-cols-[repeat(auto-fill,minmax(10rem,1fr))] gap-2 rounded border border-gray-300 p-2">
<div class="rounded bg-indigo-200 p-3 text-center">Portátiles</div>
<div class="rounded bg-indigo-200 p-3 text-center">Monitores</div>
</div>
<p class="mt-5 mb-1 text-sm font-semibold text-gray-600">auto-fit: elimina las vacías y reparte el espacio</p>
<div class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-2 rounded border border-gray-300 p-2">
<div class="rounded bg-amber-200 p-3 text-center">Portátiles</div>
<div class="rounded bg-amber-200 p-3 text-center">Monitores</div>
</div>

Figura 20. Con auto-fill quedan dos columnas vacías a la derecha. Con auto-fit desaparecen.
Los valores calculados por el navegador lo demuestran:
| Clase | Columnas calculadas |
|---|---|
auto-fill | 186.5px 186.5px 186.5px 186.5px |
auto-fit | 381px 381px 0px 0px |
Con auto-fit sigue habiendo cuatro columnas, pero las dos vacías miden cero.
Para un catálogo suele convenir auto-fill: así la última fila incompleta y los catálogos con pocos productos mantienen el tamaño de tarjeta. auto-fit encaja cuando quieres que unos pocos elementos llenen la fila.
Un catálogo sin puntos de ruptura
<ul class="grid grid-cols-[repeat(auto-fill,minmax(min(100%,13rem),1fr))] gap-4">
<li class="rounded-lg border border-gray-200 p-4">
<p class="text-xs font-semibold text-indigo-600 uppercase">Portátiles</p>
<h3 class="font-bold">Portátil UltraBook 14</h3>
<p class="mt-2 text-xl font-bold">899 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-4">
<p class="text-xs font-semibold text-indigo-600 uppercase">Portátiles</p>
<h3 class="font-bold">Portátil Gaming 16</h3>
<p class="mt-2 text-xl font-bold">1499 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-4">
<p class="text-xs font-semibold text-indigo-600 uppercase">Monitores</p>
<h3 class="font-bold">Monitor 24 FHD</h3>
<p class="mt-2 text-xl font-bold">139 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-4">
<p class="text-xs font-semibold text-indigo-600 uppercase">Monitores</p>
<h3 class="font-bold">Monitor 27 4K</h3>
<p class="mt-2 text-xl font-bold">329 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-4">
<p class="text-xs font-semibold text-indigo-600 uppercase">Telefonía</p>
<h3 class="font-bold">Smartphone Gama Media</h3>
<p class="mt-2 text-xl font-bold">299 €</p>
</li>
<li class="rounded-lg border border-gray-200 p-4">
<p class="text-xs font-semibold text-indigo-600 uppercase">Audio</p>
<h3 class="font-bold">Auriculares Bluetooth</h3>
<p class="mt-2 text-xl font-bold">59,90 €</p>
</li>
</ul>

Figura 21. A 1000 px caben cuatro columnas de 230 px.

Figura 22. A 640 px, dos columnas de 296 px.

Figura 23. A 340 px, una sola columna de 308 px.
No hay ni un md:: el mismo HTML da cuatro, dos o una columna según el espacio disponible. Además, responde al ancho del contenedor y no al de la pantalla, de modo que funciona igual dentro de un lateral estrecho.
Por qué min(100%, 13rem)
El catálogo no usa minmax(13rem,1fr), sino minmax(min(100%,13rem),1fr). El motivo aparece cuando el contenedor es más estrecho que el mínimo:
<div class="w-44 space-y-4">
<div>
<p class="mb-1 text-sm font-semibold text-red-700">minmax(13rem,1fr)</p>
<div id="sinmin" class="grid grid-cols-[repeat(auto-fill,minmax(13rem,1fr))] border-2 border-red-400">
<div class="rounded bg-red-100 p-2">Se sale</div>
</div>
</div>
<div>
<p class="mb-1 text-sm font-semibold text-green-700">minmax(min(100%,13rem),1fr)</p>
<div id="conmin" class="grid grid-cols-[repeat(auto-fill,minmax(min(100%,13rem),1fr))] border-2 border-green-500">
<div class="rounded bg-green-100 p-2">Cabe</div>
</div>
</div>
</div>

Figura 24. En un contenedor de 172 px, la pista de 13rem (208 px) se desborda. Con min(100%, 13rem) se limita al ancho disponible.
Medido: la primera rejilla da una columna de 208px con un scrollWidth de 208 en un contenedor de 172. La segunda da 172px y no desborda.
Variables CSS en las pistas 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) no existe la sintaxis con paréntesis, y
col-span-*no admite valores arbitrarios. Si usas la LTS: escribegrid-cols-[var(--columnas)]ycol-[span_var(--ancho)], que funcionan igual en las dos versiones.
Una variable CSS permite decidir la estructura desde fuera de la clase, por ejemplo con un dato que llega de JavaScript en el atributo style:
<div class="grid grid-cols-(--columnas) gap-2 [--columnas:repeat(5,minmax(0,1fr))]">
<div class="col-span-(--ancho) rounded bg-indigo-200 p-3 text-center [--ancho:3]">3 de 5</div>
<div class="col-span-(--ancho) rounded bg-amber-200 p-3 text-center [--ancho:2]">2 de 5</div>
</div>

Figura 25. Las pistas y los tamaños salen de variables CSS.
Este es el equivalente que funciona en las dos versiones. Sus medidas son idénticas, con cajas de 313,6 y 206,4 px:
<div class="grid grid-cols-[var(--columnas)] gap-2 [--columnas:repeat(5,minmax(0,1fr))]">
<div class="col-[span_var(--ancho)] rounded bg-indigo-200 p-3 text-center [--ancho:3]">3 de 5</div>
<div class="col-[span_var(--ancho)] rounded bg-amber-200 p-3 text-center [--ancho:2]">2 de 5</div>
</div>
⚠️ Importante: En v4,
grid-cols-[--columnas]compila a CSS no válido (grid-template-columns: --columnas) y la rejilla se queda sin columnas. En v3, esa misma clase sí generavar(--columnas). Es el mismo cambio que viste en el módulo 02 conbg-[--marca].
Pistas en el tema 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) no hay
@theme. Si usas la LTS: define las pistas entailwind.config.js, dentro detheme.extend, con las clavesgridTemplateColumns,gridColumn,gridTemplateRows,gridRowygridAutoRows.
Si una pista se repite por todo el proyecto, dale un nombre:
@import "tailwindcss";
@theme {
--grid-template-columns-catalogo: repeat(auto-fill, minmax(10rem, 1fr));
--grid-column-ancha: 1 / -1;
}
<div class="grid grid-cols-catalogo gap-3">
<div class="rounded bg-indigo-200 p-3 text-center">Portátiles</div>
<div class="rounded bg-indigo-200 p-3 text-center">Monitores</div>
<div class="col-ancha rounded bg-amber-200 p-3 text-center">col-ancha</div>
</div>

Figura 26. grid-cols-catalogo y col-ancha salen del tema.
El equivalente en v3, con el que el resultado medido es idéntico (188px 188px 188px):
// tailwind.config.js
export default {
content: ['./*.html'],
theme: {
extend: {
gridTemplateColumns: { catalogo: 'repeat(auto-fill, minmax(10rem, 1fr))' },
gridColumn: { ancha: '1 / -1' },
},
},
};
Espacios de nombres disponibles en v4, comprobados en 4.3.3:
| Variable | Clase que crea |
|---|---|
--grid-template-columns-* | grid-cols-* |
--grid-template-rows-* | grid-rows-* |
--grid-column-* | col-* |
--grid-column-start-* | col-start-* |
--grid-row-* | row-* |
--grid-auto-rows-* | auto-rows-* |
--grid-auto-columns-* | auto-cols-* |
Estas variables no vienen definidas en el tema por defecto: existen solo si las creas tú. El módulo 14 estudia @theme a fondo.
10. Áreas con nombre
Numerar líneas funciona, pero col-start-2 col-end-4 row-start-2 no dice nada sobre el diseño. CSS permite dibujarlo con grid-template-areas, y aunque Tailwind no trae clases para ello, las propiedades arbitrarias lo resuelven:
<div class="grid min-h-80 grid-cols-[10rem_1fr] grid-rows-[auto_1fr_auto] gap-2 [grid-template-areas:'cabecera_cabecera'_'lateral_principal'_'pie_pie']">
<header class="rounded bg-indigo-700 p-3 font-bold text-white [grid-area:cabecera]">Tienda EMPRESA</header>
<nav class="rounded bg-indigo-100 p-3 [grid-area:lateral]">Categorías</nav>
<main class="rounded bg-gray-100 p-3 [grid-area:principal]">Catálogo de productos</main>
<footer class="rounded bg-gray-700 p-3 text-center text-sm text-white [grid-area:pie]">Pie de página</footer>
</div>

Figura 27. Cada elemento declara el área que ocupa.
Cada cadena entre comillas es una fila, y cada palabra una celda. Escrito en CSS normal se lee mejor:
grid-template-areas:
"cabecera cabecera"
"lateral principal"
"pie pie";
Reglas de la sintaxis:
- Los guiones bajos se convierten en espacios, también dentro de las comillas.
- Dentro de
class="..."las comillas del valor deben ser simples. - Todas las filas deben tener el mismo número de celdas, y cada área debe formar un rectángulo. Si no, el navegador ignora la declaración entera.
[grid-area:cabecera]coloca el elemento. El nombre no lleva comillas.grid-template-areasno fija tamaños: sigues necesitandogrid-cols-*ygrid-rows-*.
Reordenar el diseño según el ancho
La gran ventaja de las áreas es que puedes cambiar el diseño tocando solo el contenedor. Cada hijo sigue diciendo “soy la cabecera”:
<div class="grid min-h-80 gap-2 [grid-template-areas:'cabecera'_'principal'_'lateral'_'pie'] md:grid-cols-[10rem_1fr] md:grid-rows-[auto_1fr_auto] md:[grid-template-areas:'cabecera_cabecera'_'lateral_principal'_'pie_pie']">
<header class="rounded bg-indigo-700 p-3 font-bold text-white [grid-area:cabecera]">Tienda EMPRESA</header>
<nav class="rounded bg-indigo-100 p-3 [grid-area:lateral]">Categorías</nav>
<main class="rounded bg-gray-100 p-3 [grid-area:principal]">Catálogo de productos</main>
<footer class="rounded bg-gray-700 p-3 text-center text-sm text-white [grid-area:pie]">Pie de página</footer>
</div>

Figura 28. A partir de 768 px, el lateral va a la izquierda.

Figura 29. En móvil, todo se apila y las categorías pasan debajo del contenido.
El orden del HTML no cambia, y eso tiene consecuencias de accesibilidad que verás en la sección 16. Aquí el cambio es inofensivo porque el lateral solo baja.
Una clase con nombre para las áreas 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) no existe
@utility. Si usas la LTS: define la clase dentro de@layer utilitiesy escribe tú la media query, como en el segundo bloque de abajo.
Una clase de 150 caracteres es difícil de leer. Si el diseño se repite, conviene darle nombre:
@utility areas-tienda {
grid-template-areas: "cabecera" "principal" "lateral";
@variant md {
grid-template-areas: "cabecera cabecera" "lateral principal";
}
}
<div class="areas-tienda grid min-h-64 gap-2 md:grid-cols-[10rem_1fr]">
<header class="rounded bg-indigo-700 p-3 font-bold text-white [grid-area:cabecera]">Tienda EMPRESA</header>
<nav class="rounded bg-indigo-100 p-3 [grid-area:lateral]">Categorías</nav>
<main class="rounded bg-gray-100 p-3 [grid-area:principal]">Catálogo de productos</main>
</div>
El equivalente en v3, que produce la misma geometría:
@layer utilities {
.areas-tienda {
grid-template-areas: "cabecera" "principal" "lateral";
}
@media (min-width: 768px) {
.areas-tienda {
grid-template-areas: "cabecera cabecera" "lateral principal";
}
}
}
@utility y @variant se estudian en el módulo 15.
11. Líneas con nombre
También puedes poner nombre a las líneas, escribiéndolo entre corchetes dentro de la propia definición de pistas. Hay un truco muy útil: si dos líneas se llaman contenido-start y contenido-end, el navegador crea un área llamada contenido, y basta con escribir col-[contenido].
El caso típico es un artículo con el texto en una columna central y algunos bloques a todo el ancho:
<article class="grid grid-cols-[[completa-start]_1fr_[contenido-start]_minmax(0,28rem)_[contenido-end]_1fr_[completa-end]] gap-y-3">
<h2 class="col-[contenido] text-xl font-bold">Nuestra tienda de Valencia</h2>
<p class="col-[contenido]">El texto se queda en una columna central cómoda de leer, y no depende del ancho de la ventana.</p>
<div class="col-[completa] rounded bg-indigo-200 p-6 text-center">Banda a todo el ancho</div>
<p class="col-[contenido]">Cada hijo elige si va al contenido o a todo el ancho, sin márgenes negativos.</p>
</article>

Figura 30. Cada hijo elige entre col-[contenido] y col-[completa].
La definición, separada por pistas, se lee así:
[completa-start] 1fr
[contenido-start] minmax(0,28rem)
[contenido-end] 1fr
[completa-end]
La solución clásica sin Grid exige mx-auto max-w-* en cada párrafo y márgenes negativos en la banda. Aquí la estructura vive en un único sitio.
12. Alineación en Grid
En el módulo 08 estudiaste items-*, self-*, justify-* y content-*. Todas funcionan también en Grid, y Grid añade dos familias más. Para ordenarlas, hazte dos preguntas: qué eje y qué se mueve.
| Eje | Toda la rejilla dentro del contenedor | Todos los elementos en su celda | Un elemento en su celda |
|---|---|---|---|
| Horizontal | justify-* | justify-items-* | justify-self-* |
| Vertical | content-* | items-* | self-* |
| Los dos | place-content-* | place-items-* | place-self-* |
La idea importante es que un elemento de la rejilla no se alinea respecto al contenedor, sino respecto a su área. Por defecto se estira para llenarla, y por eso las celdas parecen ocupar siempre todo el espacio.
justify-items-*
Flexbox no tiene equivalente, porque en una línea flexible no hay una celda horizontal propia que repartir. En Grid, cada elemento tiene su celda:
<div class="grid grid-cols-4 gap-3 text-center text-sm">
<div>
<p class="mb-1 font-semibold text-gray-600">justify-items-start</p>
<div class="grid justify-items-start gap-1 rounded border border-gray-300 p-1">
<div class="rounded bg-indigo-200 px-3 py-1">Ana</div>
<div class="rounded bg-indigo-200 px-3 py-1">Beatriz</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">justify-items-center</p>
<div class="grid justify-items-center gap-1 rounded border border-gray-300 p-1">
<div class="rounded bg-indigo-200 px-3 py-1">Ana</div>
<div class="rounded bg-indigo-200 px-3 py-1">Beatriz</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">justify-items-end</p>
<div class="grid justify-items-end gap-1 rounded border border-gray-300 p-1">
<div class="rounded bg-indigo-200 px-3 py-1">Ana</div>
<div class="rounded bg-indigo-200 px-3 py-1">Beatriz</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">justify-items-stretch</p>
<div class="grid justify-items-stretch gap-1 rounded border border-gray-300 p-1">
<div class="rounded bg-indigo-200 px-3 py-1">Ana</div>
<div class="rounded bg-indigo-200 px-3 py-1">Beatriz</div>
</div>
</div>
</div>

Figura 31. stretch es el valor efectivo por defecto: por eso las cajas llenan la celda.
| Clase | CSS |
|---|---|
justify-items-start | justify-items: start |
justify-items-center | justify-items: center |
justify-items-end | justify-items: end |
justify-items-stretch | justify-items: stretch |
justify-self-* y self-*
justify-self-* y self-* alinean un solo elemento y anulan lo que diga el contenedor:
<div class="grid h-64 grid-cols-3 grid-rows-2 gap-2 rounded border border-gray-300 p-2 text-center text-sm">
<div class="justify-self-start rounded bg-indigo-200 p-2">justify-self-start</div>
<div class="justify-self-center rounded bg-indigo-200 p-2">justify-self-center</div>
<div class="justify-self-end rounded bg-indigo-200 p-2">justify-self-end</div>
<div class="self-start rounded bg-amber-200 p-2">self-start</div>
<div class="place-self-center rounded bg-green-200 p-2">place-self-center</div>
<div class="self-end rounded bg-amber-200 p-2">self-end</div>
</div>

Figura 32. Cada elemento se alinea por su cuenta dentro de su celda.
📘 Concepto:
justify-self-startyjustify-self-endgeneranflex-startyflex-enden v4, ystartyenden v3. El nombre engaña, pero en Grid el resultado es el mismo: las posiciones medidas coinciden al píxel en las dos versiones. Solo lo verás si inspeccionas el CSS.
justify-items-normal 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: escribe la propiedad arbitraria
[justify-items:normal], que genera el mismo CSS.
Esta clase devuelve justify-items a su valor inicial. Sirve para deshacer una alineación con una variante.
place-items-*, place-content-* y place-self-*
Cada una es un atajo para los dos ejes a la vez:
<div class="grid grid-cols-3 gap-3 text-center text-sm">
<div>
<p class="mb-1 font-semibold text-gray-600">place-content-center</p>
<div class="grid h-44 grid-cols-[3rem_3rem] place-content-center gap-1 rounded border border-gray-300">
<div class="rounded bg-indigo-200 py-2">1</div>
<div class="rounded bg-indigo-200 py-2">2</div>
<div class="rounded bg-indigo-200 py-2">3</div>
<div class="rounded bg-indigo-200 py-2">4</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">place-content-between</p>
<div class="grid h-44 grid-cols-[3rem_3rem] place-content-between gap-1 rounded border border-gray-300">
<div class="rounded bg-indigo-200 py-2">1</div>
<div class="rounded bg-indigo-200 py-2">2</div>
<div class="rounded bg-indigo-200 py-2">3</div>
<div class="rounded bg-indigo-200 py-2">4</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-gray-600">place-items-center</p>
<div class="grid h-44 grid-cols-2 place-items-center gap-1 rounded border border-gray-300">
<div class="rounded bg-amber-200 px-3 py-2">1</div>
<div class="rounded bg-amber-200 px-3 py-2">2</div>
<div class="rounded bg-amber-200 px-3 py-2">3</div>
<div class="rounded bg-amber-200 px-3 py-2">4</div>
</div>
</div>
</div>

Figura 33. place-content-* mueve la rejilla entera. place-items-* mueve cada elemento dentro de su celda.
La diferencia entre las dos se ve en la figura. place-content-* solo hace algo cuando las pistas no llenan el contenedor: aquí, dos columnas de 3rem dentro de un ancho mayor. Con grid-cols-2 no habría espacio sobrante en horizontal.
Los valores admitidos son:
| Familia | Valores |
|---|---|
place-content-* | center, start, end, between, around, evenly, baseline, stretch |
place-items-* | start, end, center, baseline, stretch |
place-self-* | auto, start, end, center, stretch |
El centrado más corto de CSS
Con una sola clase de alineación, centras en los dos ejes:
<div class="grid h-56 place-items-center rounded-lg bg-indigo-50">
<p class="rounded-lg bg-white p-4 font-semibold shadow">Tu cesta está vacía</p>
</div>

Figura 34. grid place-items-center: dos clases para centrar en los dos ejes.
El equivalente en Flexbox necesita tres clases: flex items-center justify-center.
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, como
[justify-items:safe_center], que da el mismo resultado.
Centrar un elemento más grande que su pista tiene un efecto desagradable: sobresale por los dos lados, y lo que sale por el lado izquierdo queda fuera del alcance del scroll. La alineación segura lo evita: centra si cabe y se pega al inicio si no.
Las clases son justify-items-center-safe, justify-self-end-safe, place-items-center-safe, place-self-end-safe, place-content-center-safe y otras, ya presentadas para Flexbox en el módulo 08.
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<p class="mb-1 font-semibold text-red-700">justify-items-center</p>
<div class="grid h-24 grid-cols-1 justify-items-center overflow-auto rounded border-2 border-red-400 p-1">
<div class="w-72 rounded bg-red-100 p-2">Inicio visible: esta tarjeta mide 288 px</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-green-700">justify-items-center-safe (v4.1)</p>
<div class="grid h-24 grid-cols-1 justify-items-center-safe overflow-auto rounded border-2 border-green-500 p-1">
<div class="w-72 rounded bg-green-100 p-2">Inicio visible: esta tarjeta mide 288 px</div>
</div>
</div>
<div>
<p class="mb-1 font-semibold text-green-700">[justify-items:safe_center] (v3 y v4)</p>
<div class="grid h-24 grid-cols-1 overflow-auto rounded border-2 border-green-500 p-1 [justify-items:safe_center]">
<div class="w-72 rounded bg-green-100 p-2">Inicio visible: esta tarjeta mide 288 px</div>
</div>
</div>
</div>

Figura 35. En rojo, el principio del texto queda cortado y no se puede alcanzar con el scroll. En verde, la tarjeta empieza en el borde del contenedor.
Hay un detalle imprescindible: el ejemplo necesita grid-cols-1. Sin él, la columna implícita es auto, crece hasta los 288 px de la tarjeta y el elemento cabe en su pista, por lo que no hay nada que corregir. El problema solo aparece cuando la pista es más estrecha que el elemento.
Medido: en el panel rojo, que empieza en x = 16, la tarjeta empieza en x = -5,3, antes que el propio panel. En los dos verdes empieza justo tras el relleno.
13. Superponer elementos
Grid permite algo que Flexbox no: poner varios elementos en la misma celda. Solo tienes que colocarlos en el mismo sitio, con col-start-1 row-start-1:
<section class="grid overflow-hidden rounded-xl">
<div class="col-start-1 row-start-1 h-64 bg-indigo-800"></div>
<div class="col-start-1 row-start-1 place-self-center p-6 text-center text-white">
<h2 class="text-3xl font-bold">Vuelta al trabajo</h2>
<p class="mt-1">Portátiles y monitores con envío en 24 horas</p>
</div>
<span class="col-start-1 row-start-1 m-3 place-self-start rounded-full bg-amber-300 px-3 py-1 text-sm font-semibold">-15 %</span>
<a href="#" class="col-start-1 row-start-1 m-3 place-self-end rounded bg-white px-4 py-2 font-semibold text-indigo-800">Ver ofertas</a>
</section>

Figura 36. Cuatro elementos en una sola celda, cada uno alineado con place-self-*.
Comparado con position: absolute, tiene dos ventajas:
- Los elementos siguen en el flujo. El contenedor mide lo que mida el elemento más grande. Con
absolute, si el título crece y no cabe, se sale del banner; aquí la celda crece con él. - La alineación es más sencilla.
place-self-endsustituye aabsolute bottom-0 right-0, y no necesitasrelativeen el padre.
Los elementos se apilan por orden del HTML: el último queda encima. Para cambiarlo usa z-*, como en el módulo 07. En un proyecto real, el primer <div> sería un <img class="col-start-1 row-start-1 h-64 w-full object-cover">.
14. Subgrid
El problema
Tres tarjetas con títulos de distinta longitud. Cada una es una rejilla o un contenedor flexible independiente, por lo que ninguna sabe lo que mide el título de las otras, y los precios acaban a distintas alturas:
<ul class="grid grid-cols-3 gap-4">
<li class="flex flex-col rounded-lg border border-gray-200 p-3">
<h3 class="font-bold">Altavoz Inteligente</h3>
<p class="text-sm text-gray-600">Altavoz con asistente</p>
<p class="mt-2 text-xl font-bold">49 €</p>
<button class="mt-2 rounded bg-indigo-600 py-1 text-white">Añadir</button>
</li>
<li class="flex flex-col rounded-lg border border-gray-200 p-3">
<h3 class="font-bold">Portátil UltraBook 14 con nombre comercial largo</h3>
<p class="text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="mt-2 text-xl font-bold">899 €</p>
<button class="mt-2 rounded bg-indigo-600 py-1 text-white">Añadir</button>
</li>
<li class="flex flex-col rounded-lg border border-gray-200 p-3">
<h3 class="font-bold">Monitor 24 FHD</h3>
<p class="text-sm text-gray-600">Monitor de 24 pulgadas Full HD, con peana regulable en altura y dos entradas</p>
<p class="mt-2 text-xl font-bold">139 €</p>
<button class="mt-2 rounded bg-indigo-600 py-1 text-white">Añadir</button>
</li>
</ul>

Figura 37. Sin subgrid, los precios caen a 81, 125 y 121 px.
Subgrid resuelve esto: un elemento de la rejilla puede ser también una rejilla que reutiliza las pistas del padre en lugar de crear las suyas.
Figura 38. Con subgrid, las tarjetas comparten las filas del padre.
grid-rows-subgrid
Necesitas tres piezas:
- El padre es una rejilla (
grid grid-cols-3). - Cada tarjeta ocupa tantas filas como partes tiene (
row-span-4). - Cada tarjeta es una rejilla que hereda esas filas (
grid grid-rows-subgrid).
<ul class="grid grid-cols-3 gap-x-4">
<li class="row-span-4 grid grid-rows-subgrid gap-y-2 rounded-lg border border-gray-200 p-3">
<h3 class="font-bold">Altavoz Inteligente</h3>
<p class="text-sm text-gray-600">Altavoz con asistente</p>
<p class="text-xl font-bold">49 €</p>
<button class="rounded bg-indigo-600 py-1 text-white">Añadir</button>
</li>
<li class="row-span-4 grid grid-rows-subgrid gap-y-2 rounded-lg border border-gray-200 p-3">
<h3 class="font-bold">Portátil UltraBook 14 con nombre comercial largo</h3>
<p class="text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="text-xl font-bold">899 €</p>
<button class="rounded bg-indigo-600 py-1 text-white">Añadir</button>
</li>
<li class="row-span-4 grid grid-rows-subgrid gap-y-2 rounded-lg border border-gray-200 p-3">
<h3 class="font-bold">Monitor 24 FHD</h3>
<p class="text-sm text-gray-600">Monitor de 24 pulgadas Full HD, con peana regulable en altura y dos entradas</p>
<p class="text-xl font-bold">139 €</p>
<button class="rounded bg-indigo-600 py-1 text-white">Añadir</button>
</li>
</ul>

Figura 39. Con subgrid, los tres precios están a 153 px.
Medido: la posición vertical de los precios pasa de 81, 125, 121 a 153, 153, 153. Ahora la fila del título mide lo que el título más largo, y las tres tarjetas la respetan.
Tres detalles que suelen fallar:
- El número debe coincidir.
row-span-4significa cuatro filas para cuatro hijos. Un subgrid no crea pistas implícitas en el eje heredado, así que un quinto hijo no abre una fila nueva: se coloca encima del cuarto. Medido: los dos quedan en la misma posición vertical, sin ningún aviso. Si añades un hijo, cambia también elrow-span-*. - El hueco se puede cambiar. Por defecto el subgrid hereda el
gapdel padre, pero puede indicar otro. En el ejemplo, el padre solo definegap-x-4y cada tarjeta añadegap-y-2. gridsigue haciendo falta.grid-rows-subgridsolo generagrid-template-rows: subgrid. Sindisplay: gridno ocurre nada.
grid-cols-subgrid
Es lo mismo en horizontal. Sirve para filas de una lista cuyas columnas deben alinearse, sin usar una tabla:
<ul class="grid grid-cols-[auto_1fr_auto_auto] text-sm">
<li class="col-span-full grid grid-cols-subgrid gap-x-4 border-b border-gray-300 p-2 font-semibold">
<span>Pedido</span><span>Cliente</span><span>Fecha</span><span>Estado</span>
</li>
<li class="col-span-full grid grid-cols-subgrid gap-x-4 border-b border-gray-200 p-2 hover:bg-indigo-50">
<span>5001</span><span>Talleres Martín S.L.</span><span>15/01/2025</span><span>ENTREGADO</span>
</li>
<li class="col-span-full grid grid-cols-subgrid gap-x-4 border-b border-gray-200 bg-indigo-50 p-2">
<span>5002</span><span>Construcciones Levante S.A.</span><span>28/01/2025</span><span>ENTREGADO</span>
</li>
<li class="col-span-full grid grid-cols-subgrid gap-x-4 border-b border-gray-200 p-2">
<span>5004</span><span>Academia Nova</span><span>20/02/2025</span><span>ENTREGADO</span>
</li>
</ul>

Figura 40. Cada <li> tiene su propio fondo y borde, y sus columnas salen del padre.
Medido: la columna de fechas empieza en x = 489,3 en todas las filas, aunque el nombre del cliente varíe. Las columnas auto miden lo que su dato más largo en toda la lista.
contents frente a subgrid
Hay una alternativa más antigua: display: contents hace que el elemento desaparezca de la caja y sus hijos pasen a ser hijos directos de la rejilla.
<dl class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1">
<div class="contents">
<dt class="font-semibold">Referencia</dt>
<dd>Producto 1</dd>
</div>
<div class="contents">
<dt class="font-semibold">Precio</dt>
<dd>899 €</dd>
</div>
<div class="contents">
<dt class="font-semibold">Unidades en almacén</dt>
<dd>25</dd>
</div>
</dl>

Figura 41. Con contents, los <dt> y <dd> participan directamente en la rejilla.
contents | subgrid | |
|---|---|---|
| Fondo, borde o relleno en la fila | No: el elemento no genera caja | Sí |
hover: con fondo en la fila | No | Sí |
| Soporte | Muy amplio | Más reciente |
| Uso adecuado | Agrupar por semántica, sin estilo | Filas o tarjetas con estilo propio |
Compatibilidad
Tailwind genera la clase igual en v3 y en v4: grid-cols-subgrid y grid-rows-subgrid existen en las dos versiones. Lo que importa es el navegador. Según los datos de compatibilidad de MDN, subgrid está disponible desde estas versiones:
| Navegador | Grid | Subgrid |
|---|---|---|
| Chrome y Edge | 57 (Edge 16) | 117 |
| Firefox | 52 | 71 |
| Safari | 10.1 | 16 |
⚠️ Importante: Tailwind v4 admite desde Chrome 111, y subgrid llegó a Chrome en la 117. Es decir, compilar con v4 no garantiza subgrid en todos los navegadores que v4 admite. En Chrome 111-116 la declaración se ignora y las tarjetas se ven como en el caso sin subgrid: desalineadas, pero legibles.
Si quieres aplicar subgrid solo donde exista, usa supports-[...], que genera CSS en las dos versiones:
<li class="grid gap-2 supports-[grid-template-rows:subgrid]:row-span-4 supports-[grid-template-rows:subgrid]:grid-rows-subgrid">
...
</li>
15. Layouts completos
Página con el pie abajo
Es el problema clásico: si hay poco contenido, el pie sube y queda flotando. Tres filas lo resuelven:
<div class="grid min-h-dvh grid-rows-[auto_1fr_auto]">
<header class="bg-indigo-700 p-3 font-bold text-white">Tienda EMPRESA</header>
<main class="p-3">Poco contenido: el pie sigue abajo del todo.</main>
<footer class="bg-gray-700 p-3 text-sm text-white">Pie de página</footer>
</div>

Figura 42. auto para cabecera y pie, y 1fr para que el contenido se quede con el resto.
Medido con una ventana de 600 px de alto: el pie empieza en y = 556 y mide 44 px, de modo que acaba justo en el borde. La pieza clave es min-h-dvh: si fuera h-dvh, el contenido largo no podría estirar la página. La fila central es 1fr, que aquí conviene: al no haber minmax(0,...), un contenido largo hace crecer la fila.
Panel de control
El panel de la tienda combina varios niveles de rejilla: una para el lateral y el contenido, otra para las tarjetas, y otra más para las barras:
<div class="grid min-h-dvh bg-gray-100 md:grid-cols-[13rem_minmax(0,1fr)]">
<nav class="bg-indigo-900 p-4 text-indigo-100">
<p class="mb-3 font-bold text-white">EMPRESA</p>
<ul class="grid grid-cols-2 gap-2 sm:grid-cols-4 md:grid-cols-1">
<li>Resumen</li>
<li>Pedidos</li>
<li>Productos</li>
<li>Clientes</li>
</ul>
</nav>
<main class="grid auto-rows-min grid-cols-2 gap-4 p-4 lg:grid-cols-4">
<h1 class="col-span-full text-2xl font-bold">Panel de control</h1>
<section class="rounded-lg bg-white p-4">
<p class="text-sm text-gray-500">Pedidos</p>
<p class="text-2xl font-bold">24</p>
</section>
<section class="rounded-lg bg-white p-4">
<p class="text-sm text-gray-500">Pendientes</p>
<p class="text-2xl font-bold">3</p>
</section>
<section class="rounded-lg bg-white p-4">
<p class="text-sm text-gray-500">Clientes</p>
<p class="text-2xl font-bold">14</p>
</section>
<section class="rounded-lg bg-white p-4">
<p class="text-sm text-gray-500">Productos</p>
<p class="text-2xl font-bold">20</p>
</section>
<section class="col-span-full rounded-lg bg-white p-4 lg:col-span-3">
<h2 class="mb-2 font-semibold">Pedidos por estado</h2>
<div class="grid grid-cols-[auto_1fr_auto] items-center gap-x-3 gap-y-2 text-sm">
<span>Entregado</span>
<span class="h-3 rounded bg-green-500" style="width: 100%"></span>
<span>18</span>
<span>Pendiente</span>
<span class="h-3 rounded bg-amber-500" style="width: 17%"></span>
<span>3</span>
<span>Enviado</span>
<span class="h-3 rounded bg-sky-500" style="width: 11%"></span>
<span>2</span>
<span>Cancelado</span>
<span class="h-3 rounded bg-red-500" style="width: 6%"></span>
<span>1</span>
</div>
</section>
<section class="col-span-full rounded-lg bg-white p-4 lg:col-span-1">
<h2 class="mb-2 font-semibold">Sin stock</h2>
<ul class="text-sm">
<li>Pack teclado y ratón</li>
<li>Altavoz Inteligente</li>
</ul>
</section>
</main>
</div>

Figura 43. A 1200 px, cuatro indicadores en fila. El gráfico ocupa tres columnas y la lista una.

Figura 44. A 820 px se mantiene el lateral, pero los indicadores pasan a dos columnas.

Figura 45. A 400 px, el lateral pasa arriba y todo se apila.
Fíjate en estas decisiones:
md:grid-cols-[13rem_minmax(0,1fr)]: elminmaximpide que una tabla ancha empuje la página.auto-rows-minen<main>: sin él,<main>reparte su altura entre las filas y las tarjetas se estiran. Con él, cada fila mide su contenido.col-span-full lg:col-span-3: en móvil ocupa todo el ancho, y en escritorio tres de cuatro columnas. Es mucho más robusto escribircol-span-fullquecol-span-2: seguirá funcionando si cambias el número de columnas.- El gráfico también es una rejilla:
grid-cols-[auto_1fr_auto]alinea etiquetas, barras y cantidades sin calcular nada.
Los anchos style="width: 17%" de las barras son datos, no diseño, y por eso van en línea: una clase arbitraria no serviría si el dato llega en tiempo de ejecución.
Mosaico de portada
auto-rows-* con un valor fijo da filas regulares, y los span hacen el resto:
<div class="grid auto-rows-[7rem] grid-cols-2 gap-3 md:grid-cols-4">
<a href="#" class="col-span-2 row-span-2 grid place-items-center rounded-xl bg-indigo-700 text-2xl font-bold text-white">Portátiles</a>
<a href="#" class="grid place-items-center rounded-xl bg-amber-200 font-semibold">Audio</a>
<a href="#" class="row-span-2 grid place-items-center rounded-xl bg-green-200 font-semibold">Monitores</a>
<a href="#" class="grid place-items-center rounded-xl bg-pink-200 font-semibold">Telefonía</a>
<a href="#" class="col-span-2 grid place-items-center rounded-xl bg-sky-200 font-semibold md:col-span-1">Mobiliario</a>
<a href="#" class="col-span-2 grid place-items-center rounded-xl bg-gray-200 font-semibold md:col-span-3">Ofertas de la semana</a>
</div>

Figura 46. A 880 px, el mosaico de cuatro columnas.

Figura 47. A 420 px, dos columnas. Los span siguen funcionando.
Cada enlace es también una rejilla con place-items-center, para centrar el texto.
16. Accesibilidad: el orden visual no es el orden real
Grid permite poner cualquier elemento en cualquier sitio, pero eso solo cambia lo que se ve. Los lectores de pantalla y la tecla Tab siguen el orden del HTML.
<nav class="grid grid-cols-3 gap-2 text-center">
<a href="#uno" class="col-start-3 row-start-1 rounded bg-indigo-200 p-3">Primero en el HTML</a>
<a href="#dos" class="col-start-2 row-start-1 rounded bg-amber-200 p-3">Segundo en el HTML</a>
<a href="#tres" class="col-start-1 row-start-1 rounded bg-green-200 p-3">Tercero en el HTML</a>
</nav>

Figura 48. Visualmente de izquierda a derecha: tercero, segundo y primero.
Al recorrerlo con Tab en un navegador real, este es el resultado:
Primero en el HTML x=533
Segundo en el HTML x=275
Tercero en el HTML x=16
El foco salta de derecha a izquierda, justo al revés de lo que se ve. Quien navega con teclado percibe un recorrido incoherente.
Reglas prácticas:
- Escribe el HTML en el orden lógico de lectura y usa Grid para la presentación, no al revés.
- No uses
dense,order-*ni colocación manual para cambiar el orden de elementos interactivos o de contenido con una secuencia. - Reordenar está bien cuando el orden no aporta significado, como un lateral que baja o una imagen decorativa.
- Comprueba tu diseño pulsando Tab sin tocar el ratón.
17. Errores frecuentes y depuración
| Síntoma | Causa | Solución |
|---|---|---|
| Una columna empuja a las demás o la página se desborda | Pista con 1fr, que tiene mínimo auto | minmax(0,1fr) en la pista, o min-w-0 en el hijo |
grid-cols-* no hace nada | Falta grid en el mismo elemento | Añade grid |
grid-cols-15 no hace nada en v3 | Solo llega a 12 | Usa un valor arbitrario |
| Las clases afectan a los nietos, no a los hijos | Grid solo afecta a los hijos directos | Revisa la estructura, o usa subgrid |
col-span-full ocupa solo una columna | No hay columnas explícitas | Declara grid-cols-* |
| Aparecen filas o columnas de más | Un col-start-* fuera de la rejilla o elementos sobrantes | Comprueba las líneas: N columnas son N + 1 líneas |
grid-rows-* no reparte la altura | El contenedor no tiene altura definida | Añade h-* o min-h-* |
| La última fila es más baja que las demás | Es una fila implícita | Usa auto-rows-* |
| Las tarjetas se estiran sin motivo | El contenedor reparte su altura entre las filas | auto-rows-min o content-start |
| Los elementos se salen por debajo del contenedor | Una altura fija con filas minmax(0,1fr) | Usa min-h-* o auto-rows-* |
place-content-* no hace nada | Las pistas ya llenan el contenedor | Usa pistas de tamaño fijo, o place-items-* |
| Un elemento más ancho que la pantalla en catálogos | minmax(13rem,1fr) en contenedor estrecho | minmax(min(100%,13rem),1fr) |
| Las áreas no funcionan | Filas con distinto número de celdas o un área no rectangular | Revisa la cadena: si no es válida, se ignora entera |
grid-cols-2 grid-cols-12 da resultados distintos en v3 y en v4 | En v3 gana grid-cols-2 y en v4 grid-cols-12: cambia el orden de la hoja | No uses dos clases que compitan |
La última fila confirma lo que viste en el módulo 02: v3 ordena las clases como texto y v4 por valor. Se ha comprobado leyendo el orden de las reglas generadas por las dos versiones.
Inspeccionar con las herramientas del navegador
Chrome, Edge, Firefox y Safari tienen un inspector de Grid. En el panel de elementos, junto al contenedor, aparece una pequeña etiqueta grid: al pulsarla, se dibujan las líneas numeradas sobre la página. Es la forma más rápida de comprobar qué línea estás usando y si hay pistas implícitas.
Un método de depuración que funciona siempre:
- Selecciona el contenedor y mira en la pestaña de estilos calculados el valor de
grid-template-columns. El navegador muestra los anchos reales en píxeles: es lo que se ha medido en este módulo. - Si una pista mide mucho más de lo que esperabas, busca su hijo con contenido que no se parte.
- Si hay más pistas de las declaradas, busca un
col-start-*o unrow-span-*que se pase. - Aplica temporalmente
bg-red-200a los hijos: verás si llenan o no su celda.
18. Ejercicios
Ejercicio 1: Galería de producto
Crea la galería del “Portátil Gaming 16”: una imagen principal cuadrada que ocupe dos columnas y dos filas, y cuatro vistas pequeñas también cuadradas. En móvil debe tener dos columnas, y a partir de md:, cuatro.
Ver solución
<div class="grid grid-cols-2 gap-2 md:grid-cols-4">
<div class="col-span-2 row-span-2 grid aspect-square place-items-center rounded bg-indigo-300 font-bold">Portátil Gaming 16</div>
<div class="grid aspect-square place-items-center rounded bg-gray-200">Vista 2</div>
<div class="grid aspect-square place-items-center rounded bg-gray-200">Vista 3</div>
<div class="grid aspect-square place-items-center rounded bg-gray-200">Vista 4</div>
<div class="grid aspect-square place-items-center rounded bg-gray-200">Vista 5</div>
</div>

Figura 49. A 820 px, la imagen principal y las vistas a su lado.

Figura 50. A 400 px, la imagen principal ocupa las dos columnas.
col-span-2 row-span-2 ya da el comportamiento deseado en los dos tamaños, sin ninguna variante: en móvil, la imagen ocupa todo el ancho, porque solo hay dos columnas. La altura sale de aspect-square.
Ejercicio 2: Calendario
Dibuja noviembre de 2026 con los días de lunes a domingo. El mes empieza en domingo, así que el día 1 debe quedar en la séptima columna. Resalta el día 20. No uses celdas vacías.
Ver solución
<div class="max-w-md">
<h2 class="mb-2 text-center text-lg font-bold">Noviembre de 2026</h2>
<div class="grid grid-cols-7 gap-1 text-center text-sm">
<span class="font-semibold">L</span><span class="font-semibold">M</span><span class="font-semibold">X</span>
<span class="font-semibold">J</span><span class="font-semibold">V</span><span class="font-semibold">S</span>
<span class="font-semibold">D</span>
<span class="col-start-7 rounded bg-gray-100 py-1">1</span>
<span class="rounded bg-gray-100 py-1">2</span><span class="rounded bg-gray-100 py-1">3</span>
<span class="rounded bg-gray-100 py-1">4</span><span class="rounded bg-gray-100 py-1">5</span>
<span class="rounded bg-gray-100 py-1">6</span><span class="rounded bg-gray-100 py-1">7</span>
<span class="rounded bg-gray-100 py-1">8</span><span class="rounded bg-gray-100 py-1">9</span>
<span class="rounded bg-gray-100 py-1">10</span><span class="rounded bg-gray-100 py-1">11</span>
<span class="rounded bg-gray-100 py-1">12</span><span class="rounded bg-gray-100 py-1">13</span>
<span class="rounded bg-gray-100 py-1">14</span><span class="rounded bg-gray-100 py-1">15</span>
<span class="rounded bg-gray-100 py-1">16</span><span class="rounded bg-gray-100 py-1">17</span>
<span class="rounded bg-gray-100 py-1">18</span><span class="rounded bg-gray-100 py-1">19</span>
<span class="rounded bg-indigo-600 py-1 font-bold text-white">20</span>
<span class="rounded bg-gray-100 py-1">21</span><span class="rounded bg-gray-100 py-1">22</span>
<span class="rounded bg-gray-100 py-1">23</span><span class="rounded bg-gray-100 py-1">24</span>
<span class="rounded bg-gray-100 py-1">25</span><span class="rounded bg-gray-100 py-1">26</span>
<span class="rounded bg-gray-100 py-1">27</span><span class="rounded bg-gray-100 py-1">28</span>
<span class="rounded bg-gray-100 py-1">29</span><span class="rounded bg-gray-100 py-1">30</span>
</div>
</div>

Figura 51. Basta con colocar el primer día, y el resto fluye solo.
La clave es que solo el día 1 lleva posición, col-start-7. El algoritmo de colocación sigue a partir de ahí y abre fila al llegar al final, de modo que el día 2 aparece en la siguiente fila, debajo del lunes. Para otro mes solo hay que cambiar esa clase.
Ejercicio 3: Resumen del panel
En una rejilla de 6 columnas, coloca tres indicadores iguales en la primera fila. Debajo, un gráfico que ocupe 4 columnas y dos filas, con dos tarjetas apiladas a su derecha. Cierra con una barra a todo el ancho.
Ver solución
<div class="grid grid-cols-6 gap-3">
<div class="col-span-2 rounded bg-indigo-100 p-3">Pedidos: 24</div>
<div class="col-span-2 rounded bg-indigo-100 p-3">Clientes: 14</div>
<div class="col-span-2 rounded bg-indigo-100 p-3">Productos: 20</div>
<div class="col-span-4 row-span-2 rounded bg-amber-100 p-3">Ventas por mes</div>
<div class="col-span-2 rounded bg-green-100 p-3">Pendientes: 3</div>
<div class="col-span-2 rounded bg-green-100 p-3">Enviados: 2</div>
<div class="col-span-full rounded bg-gray-200 p-3 text-center">Últimos pedidos</div>
</div>

Figura 52. Seis columnas permiten mezclar mitades y tercios.
Con 6 columnas puedes repartir en tercios (col-span-2) y en mitades (col-span-3), y por eso son una elección habitual. El gráfico no necesita posición: basta con col-span-4 row-span-2, y las dos tarjetas ocupan lo que queda.
Ejercicio 4: Ficha de producto con áreas
Crea la ficha del “Portátil UltraBook 14” con cuatro áreas: imagen, datos, compra y detalles. En móvil, apiladas en ese orden. A partir de md:, imagen a la izquierda ocupando dos filas, datos y compra a la derecha, y detalles abajo a todo el ancho. El bloque de compra debe quedar pegado al final de su celda.
Ver solución
<article class="grid gap-4 [grid-template-areas:'imagen'_'datos'_'compra'_'detalles'] md:grid-cols-[3fr_2fr] md:[grid-template-areas:'imagen_datos'_'imagen_compra'_'detalles_detalles']">
<div class="grid min-h-56 place-items-center rounded-lg bg-indigo-200 [grid-area:imagen]">Imagen del producto</div>
<header class="[grid-area:datos]">
<p class="text-sm font-semibold text-indigo-600">Portátiles</p>
<h1 class="text-2xl font-bold">Portátil UltraBook 14</h1>
<p class="text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
</header>
<div class="grid gap-2 self-end [grid-area:compra]">
<p class="text-3xl font-bold">899 €</p>
<button class="rounded bg-indigo-600 py-2 font-semibold text-white">Añadir a la cesta</button>
</div>
<section class="rounded-lg bg-gray-100 p-3 [grid-area:detalles]">Quedan 25 unidades en almacén.</section>
</article>

Figura 53. A 820 px: el área imagen ocupa dos filas.

Figura 54. A 400 px: todo en una columna.
La palabra imagen se repite en dos filas para que el área ocupe las dos. self-end pega el bloque de compra al final de su celda. Solo cambia el contenedor: los hijos no llevan ninguna variante.
Ejercicio 5: Comparador con subgrid
Compara tres portátiles en tarjetas con título, precio, descripción y botón. Los títulos y las descripciones tienen longitudes distintas. Todos los precios y todos los botones deben quedar alineados.
Ver solución
<div class="grid grid-cols-3 gap-x-3">
<section class="row-span-4 grid grid-rows-subgrid gap-y-1 rounded-lg bg-gray-100 p-3">
<h3 class="font-bold">Portátil Básico 15</h3>
<p class="text-2xl font-bold">499 €</p>
<p class="text-sm">Para ofimática y clases en línea</p>
<button class="rounded bg-indigo-600 py-1 text-white">Elegir</button>
</section>
<section class="row-span-4 grid grid-rows-subgrid gap-y-1 rounded-lg bg-indigo-100 p-3">
<h3 class="font-bold">Portátil UltraBook 14</h3>
<p class="text-2xl font-bold">899 €</p>
<p class="text-sm">Ligero, de 14 pulgadas, con 16 GB de RAM: pensado para quien viaja a menudo</p>
<button class="rounded bg-indigo-600 py-1 text-white">Elegir</button>
</section>
<section class="row-span-4 grid grid-rows-subgrid gap-y-1 rounded-lg bg-gray-100 p-3">
<h3 class="font-bold">Portátil Gaming 16 con gráfica dedicada</h3>
<p class="text-2xl font-bold">1499 €</p>
<p class="text-sm">Para juegos y edición de vídeo</p>
<button class="rounded bg-indigo-600 py-1 text-white">Elegir</button>
</section>
</div>

Figura 55. Las cuatro partes de cada tarjeta comparten las filas del padre.
Cada tarjeta ocupa cuatro filas y hereda las pistas con grid-rows-subgrid. El padre solo define gap-x-3, porque el hueco vertical lo pone cada tarjeta con gap-y-1.
Ejercicio 6: Cartel de oferta
Crea un cartel de la “Mesa Elevable” con una imagen de fondo, una banda roja arriba con el texto “Últimas 6 unidades” a todo el ancho, y el nombre y el precio abajo a la derecha. No uses position: absolute.
Ver solución
<section class="grid overflow-hidden rounded-xl">
<div class="col-start-1 row-start-1 h-48 bg-gray-800"></div>
<div class="z-10 col-start-1 row-start-1 place-self-end p-4 text-right text-white">
<h2 class="text-2xl font-bold">Mesa Elevable</h2>
<p>Ahora por 399 €</p>
</div>
<p class="col-start-1 row-start-1 self-start bg-red-600 p-2 text-center font-semibold text-white">Últimas 6 unidades</p>
</section>

Figura 56. Tres capas en la misma celda.
Las tres capas comparten col-start-1 row-start-1. La banda usa self-start para irse arriba: como no tiene justify-self, sigue estirada a lo ancho. El bloque del texto usa place-self-end para ir a la esquina.
Ejercicio 7: Cronograma de proyectos 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) no existen
col-start-(--inicio)nicol-span-(--meses). Si usas la LTS: escribecol-[var(--inicio)/span_var(--meses)], que funciona igual en las dos versiones.
Dibuja un cronograma de 2025 con doce columnas de meses y una columna para el nombre. Usa tres proyectos de EMPRESA: “Migración a la nube” (enero a septiembre), “Nueva tienda online” (de junio a fin de año) y “Campaña Navidad 2025” (octubre a diciembre). Cada barra debe recibir su inicio y duración en variables CSS.
Ver solución
<div class="grid grid-cols-[11rem_repeat(12,minmax(0,1fr))] gap-y-1 text-sm">
<span></span>
<span class="text-center text-gray-500">E</span><span class="text-center text-gray-500">F</span>
<span class="text-center text-gray-500">M</span><span class="text-center text-gray-500">A</span>
<span class="text-center text-gray-500">M</span><span class="text-center text-gray-500">J</span>
<span class="text-center text-gray-500">J</span><span class="text-center text-gray-500">A</span>
<span class="text-center text-gray-500">S</span><span class="text-center text-gray-500">O</span>
<span class="text-center text-gray-500">N</span><span class="text-center text-gray-500">D</span>
<span class="col-start-1">Migración a la nube</span>
<span class="col-start-(--inicio) col-span-(--meses) rounded bg-indigo-500 [--inicio:2] [--meses:9]"></span>
<span class="col-start-1">Nueva tienda online</span>
<span class="col-start-(--inicio) col-span-(--meses) rounded bg-amber-500 [--inicio:7] [--meses:7]"></span>
<span class="col-start-1">Campaña Navidad 2025</span>
<span class="col-start-(--inicio) col-span-(--meses) rounded bg-green-500 [--inicio:11] [--meses:3]"></span>
</div>

Figura 57. Cada barra recibe su posición desde dos variables.
La línea 1 está a la izquierda de los nombres, así que enero empieza en la línea 2: el inicio es el mes más uno. col-start-1 obliga a cada nombre a empezar fila nueva.
En v3, cada barra se escribe así. Sus posiciones medidas coinciden con las de v4 (x = 192 y 606):
<div class="grid grid-cols-[11rem_repeat(12,minmax(0,1fr))] gap-y-1 text-sm">
<span class="col-start-1">Migración a la nube</span>
<span class="col-[var(--inicio)/span_var(--meses)] rounded bg-indigo-500 [--inicio:2] [--meses:9]"></span>
<span class="col-start-1">Campaña Navidad 2025</span>
<span class="col-[var(--inicio)/span_var(--meses)] rounded bg-green-500 [--inicio:11] [--meses:3]"></span>
</div>
Ejercicio 8: Corrige la rejilla
Este código debería dar un título a todo el ancho y tres departamentos en fila, pero salen todos apilados. Encuentra el motivo y corrígelo.
<div class="grid grid-rows-2 gap-2">
<h2 class="col-span-full rounded bg-amber-200 p-2">Departamentos</h2>
<div class="rounded bg-gray-100 p-2">VENTAS</div>
<div class="rounded bg-gray-100 p-2">MARKETING</div>
<div class="rounded bg-gray-100 p-2">LOGÍSTICA</div>
</div>
Ver solución
La rejilla declara filas, no columnas. Hay una sola columna: el navegador calcula grid-template-columns: 528px y cuatro filas de 40 px, dos explícitas y dos implícitas. col-span-full no tiene nada que ocupar. Basta con cambiar grid-rows-2 por grid-cols-3:
<div class="grid grid-cols-3 gap-2">
<h2 class="col-span-full rounded bg-amber-200 p-2">Departamentos</h2>
<div class="rounded bg-gray-100 p-2">VENTAS</div>
<div class="rounded bg-gray-100 p-2">MARKETING</div>
<div class="rounded bg-gray-100 p-2">LOGÍSTICA</div>
</div>

Figura 58. Antes: una columna y cuatro filas.

Figura 59. Después: tres columnas.
Ejercicio 9 (propuesto): Formulario alineado
Crea el formulario de alta de un cliente con cinco campos, cada uno dentro de su propio <div> con su etiqueta. Las etiquetas deben formar una columna tan ancha como la más larga. Resuélvelo dos veces, con contents y con grid-cols-subgrid, y añade un borde inferior a cada fila. ¿Con cuál de las dos se puede?
Ejercicio 10 (propuesto): Portada de la tienda
Une lo aprendido en una portada con esta estructura: cabecera, banner superpuesto, mosaico de categorías y catálogo con auto-fill, todo en una rejilla con líneas con nombre para que el banner vaya a todo el ancho y el resto en una columna central. El pie debe quedar abajo aunque haya pocos productos.
Resumen
| Concepto | API | Idea clave |
|---|---|---|
| Crear rejilla | grid, inline-grid | Por sí solo apila; las pistas dan la forma |
| Columnas y filas | grid-cols-*, grid-rows-* | Generan minmax(0, 1fr), que no se desborda |
| Valores dinámicos 🔄 v4.0 | grid-cols-15, col-span-13 | En v3, límite de 12 columnas y 13 líneas |
| Huecos | gap-*, gap-x-*, gap-y-* | Separan pistas, no los bordes |
| Ocupar pistas | col-span-*, row-span-* | col-span-full necesita columnas explícitas |
| Elegir líneas | col-start-*, col-end-*, -col-end-1 | N columnas son N + 1 líneas |
| Atajo 🆕 v4.1 | col-2, row-3 | No lo mezcles con col-span-* |
| Pistas implícitas | auto-rows-*, auto-cols-* | Dan tamaño a lo que crea el navegador |
| Flujo | grid-flow-col, grid-flow-row-dense | dense rompe el orden de lectura |
| Pistas a medida | grid-cols-[14rem_minmax(0,1fr)] | Con 1fr a secas, vuelve el mínimo auto |
| Rejilla adaptable | repeat(auto-fill,minmax(min(100%,13rem),1fr)) | Sin puntos de ruptura; depende del contenedor |
| Variables 🔄 v4.0 | grid-cols-(--x), col-span-(--x) | En v3, grid-cols-[var(--x)] y col-[span_var(--x)] |
| Tema 🔄 v4.0 | --grid-template-columns-* | En v3, theme.extend.gridTemplateColumns |
| Áreas | [grid-template-areas:...], [grid-area:x] | Se cambia el diseño tocando solo el contenedor |
| Líneas con nombre | [contenido-start], col-[contenido] | -start y -end crean un área |
| Alinear en la celda | justify-items-*, justify-self-* | Se alinea respecto al área, no al contenedor |
| Dos ejes | place-items-*, place-content-*, place-self-* | place-content-* mueve la rejilla entera |
| Alineación segura 🆕 v4.1 | justify-items-center-safe | Solo actúa si el elemento supera su pista |
| Superposición | col-start-1 row-start-1 | Mismo sitio, sin salir del flujo |
| Subgrid | grid-rows-subgrid, grid-cols-subgrid | Hereda las pistas; requiere Chrome 117 |
| Página completa | min-h-dvh grid-rows-[auto_1fr_auto] | El pie siempre abajo |
| Accesibilidad | Orden del HTML | Tab sigue el HTML, no lo que se ve |