Saltar al contenido
grid-avanzado.css · devschool

CSS Grid a fondo: áreas responsive, subgrid, dense y capas

Lección 21 de 50 · 17 min de lectura · Actualizado el

En esta lección
  1. Áreas responsive a fondo
  2. Tamaños que dependen del contenido
  3. Subgrid de verdad
  4. Alineación avanzada
  5. grid-auto-flow a fondo
  6. Solapar elementos
  7. Patrones de galería
  8. Depurar Grid con las DevTools
  9. Errores frecuentes
  10. Resumen

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 -start y -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:

ValorMide…
autoLo que necesite el contenido, y se estira si sobra espacio
min-contentLo mínimo posible: el ancho de la palabra más larga
max-contentLo 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:

  1. 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.
  2. Con grid-template-rows: subgrid, el título, el párrafo y el enlace de cada tarjeta se colocan en esas tres filas compartidas.
  3. 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 gap del padre. Aquí el padre tiene gap: 16px, y sin el row-gap: 8px de 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: texto si el abuelo definió esa línea.
  • El padding y el border de 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: contents en .campo para “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é alineasEn horizontalEn verticalAtajoSe pone en
Cada elemento dentro de su celdajustify-itemsalign-itemsplace-itemsContenedor
Un elemento concreto en su celdajustify-selfalign-selfplace-selfElemento
Todas las pistas dentro del contenedorjustify-contentalign-contentplace-contentContenedor

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-self y justify-self, sin calcular top ni left.
  • 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-index funciona sin necesidad de position: relative. Si quieres cambiar qué capa queda arriba, basta con z-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 2 obliga a Grid a crear una segunda columna implícita: la rejilla se descuadra y puede desbordar la pantalla. Quita los span en 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 span distinto del número de hijos con subgrid. Si la tarjeta tiene cuatro hijos y pones span 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 el gap del padre. Sobrescríbelo con row-gap en la tarjeta.
  • Usar dense en 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-content haga algo con columnas fr. Si las pistas llenan el contenedor, no sobra espacio que repartir.
  • Escribir grid-area: 1 / 2 / 3 / 4 pensando en “arriba, derecha, abajo, izquierda”. El orden es fila de inicio, columna de inicio, fila de fin, columna de fin.
  • Usar position: absolute para superponer texto e imagen y que el texto se salga en el móvil. La pila con grid-area: 1 / 1 hace que la caja crezca con su contenido.
  • Galerías con span 2 que se descuadran en móvil. Quita el span cuando solo cabe una columna.

Resumen

TécnicaPara quéEjemplo
Áreas en media queriesRediseñar sin tocar el HTMLRedibujar grid-template-areas
grid-templateÁreas, filas y columnas en una línea"a b" 1fr / 200px 1fr
nombre-start / nombre-endLíneas que crea cada áreagrid-column: contenido-start / avisos-end
max-content, fit-content()Pistas que miden lo que su contenidomax-content 1fr
subgridHijos alineados con la rejilla del padregrid-row: span 3; grid-template-rows: subgrid
-items / -self / -contentAlinear en la celda o la rejilla enteraplace-items: safe center
grid-auto-flow: denseRellenar huecos en galeríasCambia el orden visual
grid-auto-flow: columnCarrusel horizontalCon grid-auto-columns
grid-area: 1 / 1 en varios hijosApilar capas sin positionTexto sobre imagen
span + grid-auto-rows pequeñoMosaico 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).

grid-avanzado.html
Resultado

Pon a prueba lo que has aprendido

[CSS] ¿Para qué sirve subgrid?

[CSS] ¿Qué hace grid-area: 1 / 1 aplicado a dos hijos del mismo contenedor Grid?

[CSS] ¿En qué orden se escribe grid-area: 1 / 2 / 3 / 4?

[CSS] ¿Por qué justify-content no hace nada en grid-template-columns: 1fr 1fr 1fr?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.