CSS Grid a fondo: áreas responsive, subgrid, dense y capas
En esta lección
En la lección de Grid aprendiste a crear columnas con fr, a usar repeat() y minmax(), a colocar elementos por líneas o con span y a dibujar áreas con nombre. Con eso ya puedes maquetar casi cualquier página. En esta lección das el siguiente paso: áreas que se reorganizan en cada tamaño de pantalla sin tocar el HTML, subgrid para que tarjetas distintas queden perfectamente alineadas, la alineación con todos sus matices, el modo dense, elementos que se solapan en la misma celda y varios patrones de galería que verás en webs reales.
Si algo de lo básico no te suena (líneas, span, auto-fill), repásalo antes en Grid, porque aquí se da por sabido.
Áreas responsive a fondo
Las áreas con nombre son la forma más legible de maquetar, y su gran ventaja es que el diseño se cambia solo en el CSS. Vamos a ver una página de un gimnasio con tres diseños distintos.
<div class="pagina">
<header class="cabecera">Gimnasio Norte</header>
<nav class="menu">Clases · Horarios · Tarifas</nav>
<main class="contenido">Horario de esta semana</main>
<aside class="avisos">La piscina cierra el lunes</aside>
<footer class="pie">Calle del Puerto, 4</footer>
</div>
Empezamos por el móvil, con todo en una columna, y vamos añadiendo columnas al crecer la pantalla:
.pagina {
display: grid;
gap: 12px;
grid-template-areas:
"cabecera"
"menu"
"contenido"
"avisos"
"pie";
}
.cabecera { grid-area: cabecera; }
.menu { grid-area: menu; }
.contenido { grid-area: contenido; }
.avisos { grid-area: avisos; }
.pie { grid-area: pie; }
/* Tableta: los avisos suben al lado del contenido */
@media (width >= 600px) {
.pagina {
grid-template-columns: 1fr 220px;
grid-template-areas:
"cabecera cabecera"
"menu menu"
"contenido avisos"
"pie pie";
}
}
/* Escritorio: el menú pasa a una columna a la izquierda */
@media (width >= 960px) {
.pagina {
grid-template-columns: 180px 1fr 220px;
grid-template-areas:
"cabecera cabecera cabecera"
"menu contenido avisos"
"pie pie pie";
}
}
Fíjate en que cada elemento dice una sola vez a qué área pertenece. Lo que cambia en cada media query es solo el “dibujo” y las columnas. No has movido nada en el HTML, y el orden de lectura (para el teclado y los lectores de pantalla) sigue siendo el lógico.
El atajo grid-template
grid-template junta en una sola declaración las áreas, la altura de cada fila y, tras una barra, las columnas. Se lee como una tabla:
.pagina {
display: grid;
grid-template:
"cabecera cabecera" auto
"menu contenido" 1fr
"pie pie" auto
/ 200px 1fr;
}
Cada fila lleva su altura a la derecha y las columnas van al final, debajo de la columna que miden. Es cómodo cuando el diseño es pequeño; si se hace largo, vuelve a las propiedades por separado.
Las áreas crean líneas con nombre
Cuando defines un área llamada contenido, Grid crea automáticamente cuatro líneas con nombre: contenido-start y contenido-end, tanto en filas como en columnas. Puedes usarlas para colocar otro elemento alineado con esa zona:
.banner-oferta {
grid-column: contenido-start / avisos-end; /* del inicio del contenido al final de los avisos */
grid-row: pie; /* en la fila del pie */
}
Nota: estos nombres automáticos terminan siempre en
-starty-end, en inglés, aunque tus áreas tengan nombres en español.
grid-area también acepta líneas
grid-area no solo recibe un nombre. Con números es el atajo de las cuatro líneas, en este orden: fila de inicio, columna de inicio, fila de fin, columna de fin.
.mapa { grid-area: 2 / 1 / 4 / 3; } /* filas 2 a 4, columnas 1 a 3 */
El orden es “arriba, izquierda, abajo, derecha”; justo el contrario de margin, que va en sentido de las agujas del reloj. Cuesta acostumbrarse, así que muchos prefieren escribir grid-row y grid-column por separado.
Tamaños que dependen del contenido
Además de px, % y fr, las pistas pueden medir lo que pida su contenido:
| Valor | Mide… |
|---|---|
auto | Lo que necesite el contenido, y se estira si sobra espacio |
min-content | Lo mínimo posible: el ancho de la palabra más larga |
max-content | Lo que ocupa el contenido sin hacer ningún salto de línea |
fit-content(300px) | Como max-content, pero sin pasar de 300px |
Un caso típico es una ficha de producto con etiquetas a la izquierda y valores a la derecha:
.ficha {
display: grid;
grid-template-columns: max-content 1fr; /* la etiqueta más larga marca la columna */
gap: 6px 16px;
}
La primera columna mide exactamente lo que la etiqueta más larga (“Peso del producto:”), sin que tengas que adivinar un ancho en píxeles. Y una barra lateral que se adapta a su menú, pero con tope:
.panel { display: grid; grid-template-columns: fit-content(250px) 1fr; }
Subgrid de verdad
En la lección básica viste la idea de subgrid: un elemento de la rejilla que a su vez es grid, pero que en vez de crear sus propias pistas usa las del padre. Ahora vamos a resolver con él el problema más típico de las tarjetas.
El problema
Tres tarjetas de clases de un gimnasio, cada una con título, descripción y botón. Los títulos tienen distinta longitud y las descripciones también. Sin subgrid, cada tarjeta coloca sus cosas a su manera: los textos empiezan a alturas distintas y los botones quedan desalineados.
<div class="clases">
<article class="clase">
<h3>Yoga</h3>
<p>Postura, respiración y equilibrio.</p>
<a href="/yoga">Reservar</a>
</article>
<article class="clase">
<h3>Entrenamiento funcional de alta intensidad</h3>
<p>Circuitos de 40 minutos con pesas, comba y cajón.</p>
<a href="/funcional">Reservar</a>
</article>
<article class="clase">
<h3>Natación</h3>
<p>Técnica de crol y espalda.</p>
<a href="/natacion">Reservar</a>
</article>
</div>
La solución
.clases {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.clase {
display: grid;
grid-row: span 3; /* 1. la tarjeta ocupa 3 filas del padre */
grid-template-rows: subgrid; /* 2. y sus hijos usan esas 3 filas */
row-gap: 8px; /* 3. separación propia dentro de la tarjeta */
}
.clase a { align-self: end; } /* el botón, pegado abajo de su fila */
Paso a paso:
- Cada tarjeta ocupa tres filas de la rejilla de
.clases. Como el padre no define filas, Grid las crea de forma implícita, tres por cada fila de tarjetas. - Con
grid-template-rows: subgrid, el título, el párrafo y el enlace de cada tarjeta se colocan en esas tres filas compartidas. - Como todas las tarjetas de la misma fila comparten las pistas, la fila de títulos mide lo que el título más largo. Los tres párrafos empiezan a la misma altura y los tres botones quedan alineados.
El número del span tiene que coincidir con el número de hijos de la tarjeta. Si mañana añades una imagen, pasa a span 4.
Detalles que conviene saber
- Un subgrid hereda el
gapdel padre. Aquí el padre tienegap: 16px, y sin elrow-gap: 8pxde la tarjeta habría 16px entre título y párrafo. Puedes sobrescribirlo, como en el ejemplo. - Puedes hacer subgrid solo en una dirección (filas o columnas) y definir la otra libremente, o en las dos:
grid-template: subgrid / subgrid. - Las líneas con nombre del padre también llegan al subgrid, así que un nieto puede usar
grid-column: textosi el abuelo definió esa línea. - El
paddingy elborderde la tarjeta se respetan: el contenido no se sale de la caja.
Subgrid en columnas: formularios alineados
El mismo truco sirve en horizontal. Un formulario donde cada fila es un <div> con etiqueta y campo, y todas las etiquetas comparten columna:
.formulario {
display: grid;
grid-template-columns: max-content 1fr;
gap: 10px 16px;
}
.campo {
grid-column: 1 / -1; /* cada fila ocupa las dos columnas */
display: grid;
grid-template-columns: subgrid; /* etiqueta y campo usan las columnas del formulario */
}
La columna de etiquetas mide lo que la etiqueta más larga de todo el formulario, no de cada fila por separado. Y mantienes un <div> por campo en el HTML, que es cómodo para poner mensajes de error.
Consejo: antes de subgrid se usaba
display: contentsen.campopara “quitar” la caja y que sus hijos pasaran a la rejilla del abuelo. Funciona, pero pierdes la caja (no puedes darle borde ni fondo). Subgrid es la opción limpia.
Alineación avanzada
Ya conoces justify-items, align-items y place-items. Repasemos el mapa completo, porque es donde más se confunde la gente:
| Qué alineas | En horizontal | En vertical | Atajo | Se pone en |
|---|---|---|---|---|
| Cada elemento dentro de su celda | justify-items | align-items | place-items | Contenedor |
| Un elemento concreto en su celda | justify-self | align-self | place-self | Elemento |
| Todas las pistas dentro del contenedor | justify-content | align-content | place-content | Contenedor |
La regla para no liarte: justify- es el eje de las filas (horizontal en español) y align- el de las columnas (vertical). -items/-self mueven el contenido dentro de su celda; -content mueve la rejilla entera cuando sobra espacio en el contenedor.
-content solo actúa si sobra sitio
.teclado {
display: grid;
grid-template-columns: repeat(3, 60px); /* 180px de columnas */
width: 400px; /* sobran 220px */
justify-content: center; /* las tres columnas, centradas */
}
Si las columnas fueran 1fr, ocuparían todo el ancho, no sobraría nada y justify-content no tendría ningún efecto. Por eso a veces “no funciona”.
Alinear por la línea base
Con align-items: baseline, los elementos de una fila se alinean por la línea base del texto (la línea invisible donde se apoyan las letras), aunque tengan tamaños de letra distintos. Ideal para un precio grande junto a su unidad:
.precio { display: grid; grid-template-columns: auto auto; justify-content: start; align-items: baseline; gap: 4px; }
Alineación segura: safe
Si centras un elemento más grande que su celda, sobresale por los dos lados, y lo que sale por arriba o por la izquierda puede quedar fuera de la pantalla, sin forma de verlo. La palabra safe lo evita: centra mientras quepa y, si no cabe, lo alinea al inicio.
.modal { display: grid; place-items: safe center; min-height: 100dvh; }
Márgenes automáticos
En un elemento de Grid, margin: auto reparte el espacio libre de la celda, igual que en Flexbox. margin-inline-start: auto empuja el elemento al final de su celda. Normalmente es más claro usar justify-self: end, pero conviene saber que funciona.
grid-auto-flow a fondo
Por defecto, Grid coloca los elementos sin posición fila a fila y nunca vuelve atrás. Si un elemento grande no cabe al final de una fila, salta a la siguiente y deja un hueco.
.mosaico {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 100px;
grid-auto-flow: row dense;
}
.mosaico .doble { grid-column: span 2; }
Con dense, después de colocar cada elemento Grid vuelve a mirar desde el principio por si algún hueco anterior puede rellenarse con el elemento actual. El resultado es un mosaico compacto, pero el orden visual ya no coincide con el del HTML: la foto 5 puede aparecer antes que la 4. En una galería decorativa da igual; en una lista de pasos o de resultados ordenados por precio, no lo uses.
Filas que crecen en horizontal
Con grid-auto-flow: column los elementos nuevos crean columnas implícitas en lugar de filas. Combinado con grid-auto-columns y overflow-x tienes un carrusel horizontal en tres líneas:
.carrusel {
display: grid;
grid-auto-flow: column;
grid-auto-columns: min(80%, 260px); /* cada tarjeta, 260px o el 80 % si la pantalla es estrecha */
gap: 12px;
overflow-x: auto;
}
Añade scroll snap y cada tarjeta encajará al deslizar.
Solapar elementos
Grid permite algo que mucha gente no sabe: varios elementos pueden ocupar la misma celda. Se pintan uno encima de otro, en el orden del HTML (el último, arriba). Esto sustituye muchos usos de position: absolute.
La técnica de la pila
Una portada con una imagen de fondo y un texto encima:
<section class="portada">
<img src="img/ruta-lagos.jpg" alt="Lagos entre montañas al amanecer">
<div class="portada-texto">
<h1>Ruta de los Lagos</h1>
<p>12 km · 4 horas · Dificultad media</p>
</div>
</section>
.portada { display: grid; }
.portada > * { grid-area: 1 / 1; } /* todos los hijos, en la fila 1 y la columna 1 */
.portada img {
width: 100%;
height: 100%;
object-fit: cover;
}
.portada-texto {
align-self: end; /* el texto, abajo */
padding: 24px;
color: white;
background: linear-gradient(transparent, rgb(0 0 0 / 0.7));
}
Ventajas frente a position: absolute:
- La caja mide lo que el más grande de sus hijos. Si el texto crece (por ejemplo, en el móvil), la portada crece con él. Con
absolute, el texto se saldría de la imagen. - Alineas con
align-selfyjustify-self, sin calculartopnileft. - No sacas nada del flujo normal de la página.
grid-area: 1 / 1 es un atajo de grid-row: 1; grid-column: 1. También verías grid-area: pila con un área llamada pila; es lo mismo.
Solapes parciales
Como colocas por líneas, puedes hacer que dos elementos compartan parte de su espacio. Una ficha con la foto a la izquierda y un recuadro de texto que se monta sobre ella:
.ficha-libro {
display: grid;
grid-template-columns: repeat(6, 1fr);
align-items: center;
}
.ficha-libro img { grid-row: 1; grid-column: 1 / 4; } /* columnas 1 a 3 */
.ficha-libro .info { grid-row: 1; grid-column: 3 / -1; /* desde la 3 hasta el final */
z-index: 1; background: white; padding: 24px; }
La tercera columna la comparten los dos, y ahí el texto queda encima de la foto.
Nota: en los elementos de Grid (y de Flexbox),
z-indexfunciona sin necesidad deposition: relative. Si quieres cambiar qué capa queda arriba, basta conz-index. Tienes los detalles en z-index y capas.
Patrones de galería
Destacar la primera foto
.galeria {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-auto-rows: 150px;
grid-auto-flow: dense;
gap: 8px;
}
.galeria > :first-child {
grid-column: span 2;
grid-row: span 2;
}
La primera foto ocupa cuatro celdas y el resto rellena alrededor. Con dense, si la pantalla es estrecha, no quedan huecos.
Cuidado: si la galería puede quedarse con una sola columna (en un móvil muy estrecho), un elemento con
span 2obliga a Grid a crear una segunda columna implícita: la rejilla se descuadra y puede desbordar la pantalla. Quita losspanen pantallas pequeñas con una media query.
Mosaico con tamaños variados
Define un tamaño de fila pequeño y deja que cada foto diga cuántas filas ocupa según su formato:
.mosaico {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-auto-rows: 80px;
grid-auto-flow: dense;
gap: 8px;
}
.mosaico .vertical { grid-row: span 3; }
.mosaico .horizontal { grid-row: span 2; }
.mosaico .panoramica { grid-column: span 2; grid-row: span 2; }
.mosaico img { width: 100%; height: 100%; object-fit: cover; }
Las clases vertical, horizontal y panoramica se ponen en el HTML según la foto. object-fit: cover recorta cada imagen para que llene su hueco sin deformarse (lo tienes en imágenes en CSS).
¿Y el estilo Pinterest?
El mosaico tipo Pinterest, en el que cada columna se rellena con fotos de altura libre, se conoce como masonry. Los navegadores están incorporando una versión nativa en Grid, pero en 2026 todavía no funciona en todos, así que no te fíes de ella sin respaldo. Mientras tanto, las alternativas son el patrón anterior (filas pequeñas y span) o las columnas de texto con columns, que llenan columna a columna.
Depurar Grid con las DevTools
Cuando una rejilla no hace lo que esperas, no adivines: en Chrome, Edge o Firefox abre las herramientas de desarrollo (F12), selecciona el contenedor y pulsa la etiqueta grid que aparece a su lado. Verás superpuestas las líneas numeradas, los nombres de las áreas y los huecos. En el panel de Diseño (Layout) puedes activar a la vez la rejilla de varios contenedores, incluidos los subgrids.
Errores frecuentes
- Elegir un
spandistinto del número de hijos con subgrid. Si la tarjeta tiene cuatro hijos y ponesspan 3, el cuarto crea una fila implícita propia dentro de la tarjeta y se pierde la alineación. - Olvidar que el subgrid hereda el
gap. Si entre el título y el texto de una tarjeta aparece un hueco enorme, es elgapdel padre. Sobrescríbelo conrow-gapen la tarjeta. - Usar
denseen contenido con orden. Los elementos cambian de sitio visualmente y el orden del teclado ya no coincide con lo que se ve. - Esperar que
justify-contenthaga algo con columnasfr. Si las pistas llenan el contenedor, no sobra espacio que repartir. - Escribir
grid-area: 1 / 2 / 3 / 4pensando en “arriba, derecha, abajo, izquierda”. El orden es fila de inicio, columna de inicio, fila de fin, columna de fin. - Usar
position: absolutepara superponer texto e imagen y que el texto se salga en el móvil. La pila congrid-area: 1 / 1hace que la caja crezca con su contenido. - Galerías con
span 2que se descuadran en móvil. Quita elspancuando solo cabe una columna.
Resumen
| Técnica | Para qué | Ejemplo |
|---|---|---|
| Áreas en media queries | Rediseñar sin tocar el HTML | Redibujar grid-template-areas |
grid-template | Áreas, filas y columnas en una línea | "a b" 1fr / 200px 1fr |
nombre-start / nombre-end | Líneas que crea cada área | grid-column: contenido-start / avisos-end |
max-content, fit-content() | Pistas que miden lo que su contenido | max-content 1fr |
subgrid | Hijos alineados con la rejilla del padre | grid-row: span 3; grid-template-rows: subgrid |
-items / -self / -content | Alinear en la celda o la rejilla entera | place-items: safe center |
grid-auto-flow: dense | Rellenar huecos en galerías | Cambia el orden visual |
grid-auto-flow: column | Carrusel horizontal | Con grid-auto-columns |
grid-area: 1 / 1 en varios hijos | Apilar capas sin position | Texto sobre imagen |
span + grid-auto-rows pequeño | Mosaico de fotos | .vertical { grid-row: span 3 } |
Con Grid dominado, toca adaptar tus diseños a cualquier pantalla: sigue con responsive.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.