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-* y grid-rows-*, y entender por qué generan minmax(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-* y auto-cols-*
  • Cambiar el flujo de colocación con grid-flow-* y saber qué riesgos tiene dense
  • Escribir pistas a medida con valores arbitrarios: fr, minmax(), repeat(), auto-fill y auto-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 familia place-*
  • 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>

Cinco categorías colocadas con Flexbox y con Grid. En Flexbox las dos de abajo se estiran hasta ocupar toda la fila. En Grid mantienen el ancho de su columna

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.

Rejilla de tres columnas y dos filas con las líneas numeradas del 1 al 4 arriba y del -4 al -1 abajo, una celda, un área de dos celdas y una pista que ocupa una fila

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

La misma rejilla de dos etiquetas. Con grid ocupa una línea completa y parte la frase. Con inline-grid queda dentro de la frase

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>

Siete cajas numeradas en tres columnas iguales. La séptima queda sola en la tercera fila

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&amp;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&amp;orden=descendente 200 12ms</pre>
  </div>
  <div class="rounded bg-indigo-100 p-2">Resumen</div>
</div>

Dos rejillas con el mismo contenido. Con 1fr la primera columna ocupa casi todo y la columna Resumen se sale del recuadro rojo. Con grid-cols-2 las dos columnas son iguales y el texto largo se desplaza dentro de su caja

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&amp;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-0 cambia 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>

Ocho cajas en cuatro columnas, con mucha separación horizontal y poca vertical

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-* y grid-rows-* solo llegan a 12, y grid-cols-15 no genera nada, sin ningún aviso. Si usas la LTS: escribe la pista completa con grid-cols-[repeat(15,minmax(0,1fr))], o añade el valor a gridTemplateColumns en tailwind.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>

Dos rejillas de quince columnas idénticas, la primera con grid-cols-15 y la segunda con un valor arbitrario

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>

Dos rejillas de tres filas. La de la izquierda llena una altura fija de 224 píxeles. La de la derecha es más baja y sus tres filas miden lo mismo que la más alta

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>

Rejilla de cuatro columnas con una caja que ocupa dos columnas, otra que ocupa dos filas, varias normales y una última que ocupa toda la fila

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>

Cinco barras de colores en una rejilla de seis columnas, colocadas con distintas combinaciones de líneas de inicio, de fin y negativas

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-9 en una rejilla de grid-cols-6 añ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>

Dos rejillas de tres columnas con una caja col-span-full. En la de arriba, sin columnas declaradas, ocupa solo la primera columna. En la de abajo ocupa las tres

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-2 y row-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>

Tres cajas colocadas por columna y fila en una rejilla de cuatro columnas, dejando el resto de celdas vacías

Figura 12. Colocar con col-* y row-* deja libres las demás celdas.

⚠️ Importante: No combines col-3 con col-span-2. Las dos generan la propiedad grid-column, así que una anula a la otra. Tailwind escribe col-3 antes en la hoja, por lo que gana col-span-2 y la posición se pierde. Para indicar inicio y tamaño, usa col-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.

Seis cajas dentro de un marco azul que representa la rejilla explícita y dos cajas en un marco naranja discontinuo que representa la fila 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>

Dos rejillas. En la primera las dos filas declaradas miden 71 píxeles y la implícita 32. En la segunda todas miden 80 píxeles

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>

Tres columnas que comparan auto-rows-auto, auto-rows-fr con filas iguales y auto-rows-min con el espacio sobrante abajo

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>

Siete cajas numeradas colocadas de dos formas. A la izquierda de izquierda a derecha, a la derecha de arriba abajo con una cuarta columna

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>

Tres tarjetas de producto de igual ancho y una cuarta cortada por el borde, dentro de un carril con desplazamiento horizontal

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>

Dos mosaicos con los mismos siete elementos. A la izquierda quedan huecos. A la derecha los elementos 6 y 7 suben a rellenarlos y la rejilla queda compacta

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: dense es 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>

Cuatro rejillas con barras de distintos anchos que muestran pistas fijas, flexibles, proporcionales y limitadas con minmax

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, escribe grid-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-fill crea las columnas que quepan, aunque se queden vacías.
  • auto-fit crea las mismas y luego colapsa a 0 las vacías, de modo que 1fr reparte 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>

Dos elementos en una rejilla ancha. Con auto-fill ocupan solo las dos primeras columnas de cuatro. Con auto-fit se reparten todo el ancho

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>

Catálogo de seis productos en cuatro columnas a 1000 píxeles de ancho

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

El mismo catálogo en dos columnas a 640 píxeles

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

El mismo catálogo en una columna a 340 píxeles

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>

Dos contenedores de 176 píxeles. En el primero la caja se sale por la derecha. En el segundo cabe

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: escribe grid-cols-[var(--columnas)] y col-[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>

Una rejilla de cinco columnas con dos cajas, una de tres columnas y otra de dos

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í genera var(--columnas). Es el mismo cambio que viste en el módulo 02 con bg-[--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 en tailwind.config.js, dentro de theme.extend, con las claves gridTemplateColumns, gridColumn, gridTemplateRows, gridRow y gridAutoRows.

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>

Rejilla con tres columnas automáticas y una caja que ocupa todo el ancho, creadas con clases propias

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>

Página con cabecera arriba, lateral a la izquierda, contenido principal a la derecha y pie de página abajo

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-areas no fija tamaños: sigues necesitando grid-cols-* y grid-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>

La página a 800 píxeles, con el lateral a la izquierda del contenido

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

La página a 400 píxeles, con todo apilado en una columna y las categorías debajo del contenido

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

Artículo con el título y los párrafos en una columna central y una banda de color que ocupa todo el ancho

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>

Cuatro rejillas con dos nombres alineados a la izquierda, al centro, a la derecha y estirados

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>

Seis cajas colocadas cada una en un extremo o en el centro de su celda en una rejilla de tres columnas por dos filas

Figura 32. Cada elemento se alinea por su cuenta dentro de su celda.

📘 Concepto: justify-self-start y justify-self-end generan flex-start y flex-end en v4, y start y end en 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>

Tres rejillas. Con place-content-center el bloque de cajas va en el centro. Con between las cajas van a las esquinas. Con place-items-center cada caja va en el centro de su propia celda

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>

Mensaje Tu cesta está vacía centrado en horizontal y en vertical dentro de un recuadro

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>

Tres cajas con una tarjeta más ancha que su contenedor. En la primera, el inicio del texto queda cortado por la izquierda. En las otras dos el texto empieza en el borde

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>

Banner azul con un título centrado, una etiqueta de descuento en la esquina superior izquierda y un botón en la inferior derecha

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-end sustituye a absolute bottom-0 right-0, y no necesitas relative en 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>

Tres tarjetas de producto en las que el precio y el botón quedan a alturas distintas

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.

Dos grupos de tarjetas. A la izquierda cada una tiene sus propias filas y los precios no se alinean. A la derecha todas comparten las filas del padre

Figura 38. Con subgrid, las tarjetas comparten las filas del padre.

grid-rows-subgrid

Necesitas tres piezas:

  1. El padre es una rejilla (grid grid-cols-3).
  2. Cada tarjeta ocupa tantas filas como partes tiene (row-span-4).
  3. 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>

Las mismas tres tarjetas, ahora con los títulos, descripciones, precios y botones perfectamente alineados entre sí

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-4 significa 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 el row-span-*.
  • El hueco se puede cambiar. Por defecto el subgrid hereda el gap del padre, pero puede indicar otro. En el ejemplo, el padre solo define gap-x-4 y cada tarjeta añade gap-y-2.
  • grid sigue haciendo falta. grid-rows-subgrid solo genera grid-template-rows: subgrid. Sin display: grid no 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>

Lista de pedidos en la que cada fila tiene borde inferior y las cuatro columnas están alineadas, con una fila resaltada

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>

Lista de datos de un producto con etiquetas en negrita en una columna y valores alineados en otra

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>

Página con cabecera, una línea de texto y el pie pegado al borde inferior de la ventana

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>

Panel de control con lateral oscuro, cuatro indicadores en fila, un gráfico de barras de pedidos por estado y una lista de productos sin stock

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

El mismo panel a 820 píxeles, con el lateral y los indicadores de dos en dos

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

El mismo panel a 400 píxeles, con el menú arriba y todas las secciones apiladas

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)]: el minmax impide que una tabla ancha empuje la página.
  • auto-rows-min en <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 escribir col-span-full que col-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>

Mosaico de categorías con un bloque grande de portátiles y bloques de distintos tamaños

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

El mismo mosaico en dos columnas a 420 píxeles

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>

Tres enlaces en fila. El tercero del HTML aparece a la izquierda y el primero a la derecha

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:

  1. 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.
  2. Si una pista mide mucho más de lo que esperabas, busca su hijo con contenido que no se parte.
  3. Si hay más pistas de las declaradas, busca un col-start-* o un row-span-* que se pase.
  4. Aplica temporalmente bg-red-200 a 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>

Galería con imagen principal grande y cuatro vistas pequeñas a su derecha

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

La galería en dos columnas con la imagen principal arriba

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>

Calendario de noviembre de 2026 con el día 1 en la columna del domingo y el día 20 resaltado

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>

Panel con tres indicadores arriba, un gráfico grande con dos tarjetas a la derecha y una barra inferior

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>

Ficha con la imagen a la izquierda, datos y compra a la derecha y detalles abajo

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

Ficha en móvil con todas las áreas apiladas

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>

Tres tarjetas de portátiles con precios y botones alineados pese a tener textos de distinta longitud

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>

Cartel oscuro con banda roja superior y el nombre y el precio abajo a la derecha

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) ni col-span-(--meses). Si usas la LTS: escribe col-[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>

Cronograma con tres barras de colores sobre doce columnas de meses

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>

Título y tres departamentos apilados en una columna

Figura 58. Antes: una columna y cuatro filas.

Título a todo el ancho y tres departamentos en fila

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