Maquetar con Grid

Flexbox reparte en una dirección: una fila, o una columna. Grid reparte en las dos a la vez, y por eso puede alinear lo que hay en una fila con lo que hay en la de al lado.

La otra diferencia es dónde se escribe: en Flexbox el reparto lo declara cada hijo con flex; en Grid lo declara el padre, de una vez, con grid-template-columns y grid-template-rows.

Recordatorio. Un grid de 3 columnas tiene 4 líneas verticales, y una de 2 filas tiene 3 horizontales: siempre hay una línea más que pistas. Las propiedades del padre definen las pistas; las de los hijos se refieren a las líneas.

1. El layout de la página, con áreas nombradas

En lugar de contar líneas, se dibuja la maqueta con nombres. Cada palabra es una celda, y repetir un nombre en celdas contiguas hace que esa zona se extienda.

body {
  display: grid;
  grid-template-columns: 1fr 3fr;
  grid-template-areas:
    "cabecera cabecera"
    "lateral  contenido"
    "pie      pie";
}

header { grid-area: cabecera; }
aside  { grid-area: lateral; }
main   { grid-area: contenido; }
footer { grid-area: pie; }
<div class="pagina">
    <div class="cabecera">cabecera</div>
    <div class="lateral">lateral</div>
    <div class="contenido">contenido</div>
    <div class="pie">pie</div>
  </div>

El dibujo del CSS tiene la forma de la página, así que se lee de un vistazo y se reordena cambiando solo esas tres líneas. Para pasar el lateral a la derecha basta con escribir "contenido lateral" y no se toca el HTML.

Si una celda debe quedar vacía, se escribe un punto: "cabecera ."

2. La galería

Toda galería se reorganiza sola. No se escriben medidas de pantalla: se le dice al navegador el ancho mínimo que admite una foto, y él decide cuántas caben.

.galeria {
  display: grid;
  gap: .6rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  grid-auto-rows: 9rem;
}

minmax(11rem, 1fr) quiere decir «ninguna columna por debajo de 11 rem, y el resto del espacio repartido entre todas». auto-fit es «pon las que quepan».

<div class="galeria">
    <img class="foto" src="IMG/naturaleza-lago-bn.jpg"
         alt="Lago entre montañas en blanco y negro">
    <img class="foto" src="IMG/naturaleza-arroyo.jpg"
         alt="Arroyo de montaña en un bosque de Trentino">
    <img class="foto" src="IMG/naturaleza-abetos.jpg"
         alt="Arroyo entre abetos bajo un cielo nublado">
    <img class="foto" src="IMG/naturaleza-valle.jpg"
         alt="Valle con niebla al amanecer">
    <img class="foto" src="IMG/retrato-contraluz.jpg"
         alt="Retrato de mujer a contraluz">
    <img class="foto" src="IMG/retrato-rizado.jpg"
         alt="Primer plano de mujer con pelo rizado">
    <img class="foto" src="IMG/retrato-panuelo.jpg"
         alt="Retrato de hombre con sombrero y pañuelo rojo">
    <img class="foto" src="IMG/bn-caballo.jpg"
         alt="Caballo en blanco y negro">
    
  </div>

Estrecha la ventana y verás cambiar el número de columnas sin que haya una sola medida de pantalla escrita.

Quién decide el alto de las filas

En el ejemplo anterior las filas miden 9rem porque lo dice grid-auto-rows. No es la única manera: el alto puede venir de la propia foto. Las dos vías dan el mismo resultado visual y se escriben distinto.

Primera vía: la proporción de la foto. El grid no dice nada del alto, y cada foto declara la suya con aspect-ratio. Como todas llevan la misma, la fila sale pareja.

.galeria {
  display: grid;
  gap: .6rem;
  grid-template-columns: repeat(3, 1fr);
}

.foto {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;     /* tres de ancho por dos de alto */
  object-fit: cover;
}
<div class="galeria">
  <img class="foto" src="IMG/naturaleza-lago-bn.jpg"
       alt="Lago entre montañas en blanco y negro">
  <img class="foto" src="IMG/naturaleza-arroyo.jpg"
       alt="Arroyo de montaña en un bosque de Trentino">
  <img class="foto" src="IMG/naturaleza-abetos.jpg"
       alt="Arroyo entre abetos bajo un cielo nublado">
  <img class="foto" src="IMG/naturaleza-valle.jpg"
       alt="Valle con niebla al amanecer">
  <img class="foto" src="IMG/retrato-contraluz.jpg"
       alt="Retrato de mujer a contraluz">
  <img class="foto" src="IMG/retrato-rizado.jpg"
       alt="Primer plano de mujer con pelo rizado">
</div>

Segunda vía: decirle a cada foto entre qué líneas va. El grid fija el alto de la fila y cada foto declara su columna y su fila, una por una. Es más largo de escribir, pero permite colocarlas en cualquier orden, con independencia del que tengan en el HTML.

.galeria {
  display: grid;
  gap: .6rem;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 8rem;
}

.foto {
  display: block;
  width: 100%;
  height: 100%;          /* llena el alto que el grid le ha dado */
  object-fit: cover;
}

.f1 { grid-column-start: 1; grid-column-end: 2; grid-row-start: 1; grid-row-end: 2; }
.f2 { grid-column-start: 2; grid-column-end: 3; grid-row-start: 1; grid-row-end: 2; }
.f3 { grid-column-start: 3; grid-column-end: 4; grid-row-start: 1; grid-row-end: 2; }
.f4 { grid-column-start: 1; grid-column-end: 2; grid-row-start: 2; grid-row-end: 3; }
.f5 { grid-column-start: 2; grid-column-end: 3; grid-row-start: 2; grid-row-end: 3; }
.f6 { grid-column-start: 3; grid-column-end: 4; grid-row-start: 2; grid-row-end: 3; }
<div class="galeria">
  <img class="foto f1" src="IMG/naturaleza-lago-bn.jpg"
       alt="Lago entre montañas en blanco y negro">
  <img class="foto f2" src="IMG/naturaleza-arroyo.jpg"
       alt="Arroyo de montaña en un bosque de Trentino">
  <img class="foto f3" src="IMG/naturaleza-abetos.jpg"
       alt="Arroyo entre abetos bajo un cielo nublado">
  <img class="foto f4" src="IMG/naturaleza-valle.jpg"
       alt="Valle con niebla al amanecer">
  <img class="foto f5" src="IMG/retrato-contraluz.jpg"
       alt="Retrato de mujer a contraluz">
  <img class="foto f6" src="IMG/retrato-rizado.jpg"
       alt="Primer plano de mujer con pelo rizado">
</div>

Estreche la ventana y compare los dos. No se comportan igual. Con aspect-ratio, al reducirse el ancho se reduce el alto en la misma medida y la foto conserva su forma. Con grid-auto-rows, el alto no se mueve —son 8rem siempre—, así que al estrecharse la columna la celda se vuelve cada vez más apaisada y object-fit: cover recorta más foto.

Lo que no se puede es mezclarlas. Si una foto lleva height: 100% y aspect-ratio a la vez, solo actúa la primera; la otra declaración queda escrita sin hacer nada, y quien lea el CSS no tiene forma de saber cuál manda.

Para una galería corriente conviene la primera, porque respeta el encuadre de la foto a cualquier ancho. La segunda se usa cuando el orden en pantalla no coincide con el del HTML, o cuando alguna foto debe ocupar más de una celda, que es lo siguiente.

Cada foto entre las líneas que se quiera

Hasta aquí las fotos caen donde toca. Pero se puede decir de qué línea a qué línea va cada una, y entonces cada foto ocupa el tamaño que se le dé:

.galeria {
  display: grid;
  gap: .6rem;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 7rem;
}

.destacada {
  grid-column-start: 1;  grid-column-end: 3;   /* de la línea 1 a la 3: dos columnas */
  grid-row-start: 1;     grid-row-end: 3;      /* de la línea 1 a la 3: dos filas    */
}

.ancha {
  grid-column-start: 3;  grid-column-end: 5;   /* dos columnas, una sola fila */
}

.alta {
  grid-row-start: 2;     grid-row-end: 4;      /* una columna, dos filas */
}
<div class="mosaico">
    <img class="foto destacada" src="IMG/naturaleza-valle.jpg"
         alt="Valle con niebla al amanecer">
    <img class="foto ancha" src="IMG/ciudad-aerea.jpg"
         alt="Vista aérea de una ciudad extensa">
    <img class="foto alta" src="IMG/bn-cupula.jpg"
         alt="Cúpula del Palacio de Bellas Artes en blanco y negro">
    <img class="foto" src="IMG/naturaleza-arroyo.jpg"
         alt="Arroyo de montaña en un bosque de Trentino">
    <img class="foto" src="IMG/retrato-rizado.jpg"
         alt="Primer plano de mujer con pelo rizado">
    <img class="foto" src="IMG/bn-hombre.jpg"
         alt="Retrato en blanco y negro de hombre pensativo">
    <img class="foto" src="IMG/ciudad-amanecer.jpg"
         alt="Rascacielos de Ciudad de México al amanecer">
    
  </div>

Con este mecanismo se construye cualquier composición: piezas destacadas, bandas anchas, columnas altas. Es una sola idea —decir entre qué líneas va cada elemento— y de ella sale todo lo demás.

Sin clases: elegir por el número de orden

Las tres clases anteriores —destacada, ancha, alta— hay que escribirlas a mano en el HTML, una por foto. Si lo que se quiere es tratar de forma distinta a la primera, a la segunda y a la tercera, el CSS puede elegirlas por su posición, sin tocar el HTML: :nth-child(n) designa al hijo que ocupa el lugar n.

.mosaico .foto:nth-child(1) { /* la primera */ }
.mosaico .foto:nth-child(2) { /* la segunda */ }
.mosaico .foto:nth-child(3) { /* la tercera */ }

Se cuenta desde uno, no desde cero. Para los extremos existen además :first-child y :last-child, que son lo mismo escrito más corto.

<div class="mosaico">
  <img class="foto" src="IMG/naturaleza-valle.jpg"
       alt="Valle con niebla al amanecer">
  <img class="foto" src="IMG/ciudad-aerea.jpg"
       alt="Vista aérea de una ciudad extensa">
  <img class="foto" src="IMG/bn-cupula.jpg"
       alt="Cúpula del Palacio de Bellas Artes en blanco y negro">
  <img class="foto" src="IMG/naturaleza-arroyo.jpg"
       alt="Arroyo de montaña en un bosque de Trentino">
  <img class="foto" src="IMG/retrato-rizado.jpg"
       alt="Primer plano de mujer con pelo rizado">
  <img class="foto" src="IMG/bn-hombre.jpg"
       alt="Retrato en blanco y negro de hombre pensativo">
  <img class="foto" src="IMG/ciudad-amanecer.jpg"
       alt="Rascacielos de Ciudad de México al amanecer">
</div>

El resultado es idéntico al del ejemplo anterior, pero aquí las siete fotos llevan la misma clase foto y es el CSS el que decide cuál va dónde. La diferencia práctica: si se cambia el orden de las fotos en el HTML, la composición se mantiene y lo que cambia es qué foto ocupa cada hueco.

Hay un detalle que sorprende la primera vez: :nth-child cuenta todos los hermanos, no solo los del mismo tipo. Si entre las fotos hubiera un párrafo, contaría también y el reparto se descolocaría.

Composiciones superpuestas

Nada obliga a que dos elementos ocupen celdas distintas. Si sus líneas se solapan, se solapan ellos, y así se monta un collage: una pieza grande y otras encima de una parte de ella. El grid se divide en muchas columnas y filas pequeñas, y cada foto declara entre qué líneas va.

.collage {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: 3.5rem;     /* celdas pequeñas: más sitios donde encajar */
}

.pieza {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: .25rem solid #fff;  /* el filo blanco separa una foto de otra */
}

.p1 { grid-column-start: 1; grid-column-end: 6; grid-row-start: 1; grid-row-end: 5; }
.p2 { grid-column-start: 4; grid-column-end: 7; grid-row-start: 3; grid-row-end: 6; }
.p3 { grid-column-start: 1; grid-column-end: 3; grid-row-start: 4; grid-row-end: 6; }

La p1 llega hasta la línea 6 y la p2 arranca en la 4: las columnas 4 y 5 las ocupan las dos. Con las filas pasa igual. En esa zona compartida una tapa a la otra, y por eso la p2 y la p3 aparecen montadas sobre la foto grande y sobresaliendo por abajo.

<div class="collage">
  <img class="pieza p1" src="IMG/naturaleza-valle.jpg"
       alt="Valle con niebla al amanecer">
  <img class="pieza p2" src="IMG/ciudad-aerea.jpg"
       alt="Vista aérea de una ciudad extensa">
  <img class="pieza p3" src="IMG/retrato-rizado.jpg"
       alt="Primer plano de mujer con pelo rizado">
</div>

Quién queda encima lo decide el orden del HTML: el último se dibuja sobre los anteriores. Para alterarlo no hace falta cambiar el HTML, basta z-index, que es el número de capa:

.p1 { z-index: 2; }   /* la grande, por delante de las otras dos */

Aquí no hay gap. Un gap separa las celdas, y lo que se busca es justo lo contrario. La separación entre fotos la da el border blanco de cada una.

Obsérvese que el HTML no sabe nada de esto: son tres imágenes seguidas, sin anidar y sin contenedores intermedios. Toda la composición está en el CSS, y cambiarla es cambiar números de línea.

3. Grids dentro de grids

Un elemento puede ser hijo de un grid y, a la vez, ser grid él mismo. Así se monta una página entera: un grid para el cuerpo, otra para el contenido y otra para una subdivisión dentro de él.

body { display: grid; grid-template-rows: auto 1fr auto; }  /* cabecera, centro, pie */

main { display: grid; grid-template-columns: 1fr 2fr; }     /* lateral y contenido   */

.fichas { display: grid; grid-template-columns: 1fr 1fr; }  /* dentro del contenido  */
<div class="pagina">
    <div class="cabecera">cabecera</div>
    <div class="centro">
      <div class="caja">lateral</div>
      <div class="nested-grid">
        <div class="caja">ficha</div><div class="caja">ficha</div>
        <div class="caja">ficha</div><div class="caja">ficha</div>
      </div>
    </div>
    <div class="pie">pie</div>
  </div>

Cada grid es independiente: la del main no sabe nada de las columnas del body. Eso permite cambiar una sin tocar las demás.

grid-template-rows: auto 1fr auto merece atención: la cabecera y el pie ocupan lo que necesiten, y el centro se queda con todo lo que sobre. Con eso el pie siempre queda abajo, sin depender de que las alturas sumen exactamente la pantalla.

4. El formulario

Aquí Grid hace algo que Flexbox no puede: alinear una columna consigo misma a lo largo de muchas filas.

form {
  display: grid;
  grid-template-columns: auto 1fr;   /* etiqueta y campo */
  gap: 10px;
  align-items: center;
}
<div class="formulario">
    <span class="etiqueta">Nombre</span><div class="campo"></div>
    <span class="etiqueta">Correo electrónico</span><div class="campo"></div>
    <span class="etiqueta">Teléfono</span><div class="campo"></div>
    <span class="etiqueta">Mensaje</span><div class="campo-alto"></div>
    <div class="boton">Enviar</div>
  </div>

Las etiquetas miden cosas distintas, pero la columna se ajusta a la más larga y todos los campos empiezan en la misma vertical. Con Flexbox habría que hacer cuatro filas independientes y darle a cada etiqueta un ancho fijo calculado a ojo, porque una fila no sabe lo que mide la de arriba.

auto en la primera columna significa «lo que necesite su contenido», y 1fr en la segunda, «todo lo demás».

El botón está colocado con grid-column-start: 2 para que arranque donde empiezan los campos, y justify-self: start para que no se estire a lo ancho.

Lo mismo vale para una tabla de precios o de características: las filas de una columna se alinean con las de la columna de al lado.

Reconocimientos

Las fotos son de Pexels, con licencia libre: se pueden usar y modificar sin pagar y sin citar la fuente. Citarla no es obligatorio, pero es lo correcto, y aquí sirve además de ejemplo de cómo se hace.