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.
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>
.pagina { display: grid; gap: 8px; grid-template-columns: 1fr 3fr; grid-template-areas: 'cabecera cabecera' 'lateral contenido' 'pie pie'; } .cabecera { grid-area: cabecera; background: #2a2a55; color: #fff; padding: 14px; text-align: center; } .lateral { grid-area: lateral; background: #c4c4dd; padding: 14px; text-align: center; min-height: 120px; } .contenido { grid-area: contenido; background: #e8e8f2; padding: 14px; text-align: center; min-height: 120px; } .pie { grid-area: pie; background: #4a4a85; color: #fff; padding: 10px; text-align: center; }
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 ."
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>
.galeria { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit,minmax(11rem,1fr)); grid-auto-rows: 9rem; } .foto { display: block; width: 100%; height: 100%; object-fit: cover; }
Estrecha la ventana y verás cambiar el número de columnas sin que haya una sola medida de pantalla escrita.
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>
.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; }
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>
.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; }
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.
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>
.mosaico { display: grid; gap: .6rem; grid-template-columns: repeat(4,1fr); grid-auto-rows: 7rem; } .foto { display: block; width: 100%; height: 100%; object-fit: cover; } .destacada { grid-column-start: 1; grid-column-end: 3; grid-row-start: 1; grid-row-end: 3; } .ancha { grid-column-start: 3; grid-column-end: 5; grid-row-start: 1; grid-row-end: 2; } .alta { grid-row-start: 2; grid-row-end: 4; }
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.
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>
.mosaico { display: grid; gap: .6rem; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 7rem; } .foto { display: block; width: 100%; height: 100%; object-fit: cover; } /* la primera foto: dos columnas y dos filas */ .mosaico .foto:nth-child(1) { grid-column-start: 1; grid-column-end: 3; grid-row-start: 1; grid-row-end: 3; } /* la segunda: dos columnas, una fila */ .mosaico .foto:nth-child(2) { grid-column-start: 3; grid-column-end: 5; } /* la tercera: una columna, dos filas */ .mosaico .foto:nth-child(3) { grid-row-start: 2; grid-row-end: 4; }
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.
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>
.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; }
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.
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>
.pagina { display: grid; gap: 8px; grid-template-rows: auto 1fr auto; border: 2px dashed #888; padding: 8px; } .cabecera { background: #2a2a55; color: #fff; padding: 14px; text-align: center; } .centro { display: grid; gap: 8px; grid-template-columns: 1fr 2fr; border: 2px solid #2a5db0; padding: 8px; } .caja { background: #e8e8f2; border: 1px solid #8686a8; padding: 12px; text-align: center; font-size: .85em; } .nested-grid { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; border: 2px solid #7a9a7a; padding: 8px; } .pie { background: #4a4a85; color: #fff; padding: 10px; text-align: center; }
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.
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>
.formulario { display: grid; gap: 10px; grid-template-columns: auto 1fr; align-items: center; max-width: 30rem; } .etiqueta { font-size: .9em; } .campo { border: 1px solid #8686a8; background: #fff; padding: 8px; min-height: 1.2em; } .campo-alto { border: 1px solid #8686a8; background: #fff; padding: 8px; min-height: 4em; } .boton { grid-column-start: 2; justify-self: start; background: #2a2a55; color: #fff; padding: 8px 18px; }
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.
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.
naturaleza-lago-bn.jpg — Lago entre montañas en blanco y negro. Foto de Aysegul Aytoren, ficha en Pexels.naturaleza-arroyo.jpg — Arroyo de montaña en un bosque de Trentino. Foto de Pluto91 Operatic, ficha en Pexels.naturaleza-abetos.jpg — Arroyo entre abetos bajo un cielo nublado. Foto de Snapwire, ficha en Pexels.naturaleza-valle.jpg — Valle con niebla al amanecer. Foto de Johannes Plenio, ficha en Pexels.retrato-contraluz.jpg — Retrato de mujer a contraluz. Foto de Asep Awaludin, ficha en Pexels.retrato-rizado.jpg — Primer plano de mujer con pelo rizado. Foto de Eric Quinones, ficha en Pexels.retrato-panuelo.jpg — Retrato de hombre con sombrero y pañuelo rojo. Foto de Aa Dil, ficha en Pexels.bn-hombre.jpg — Retrato en blanco y negro de hombre pensativo. Foto de Samuel Moses, ficha en Pexels.bn-cupula.jpg — Cúpula del Palacio de Bellas Artes en blanco y negro. Foto de ἐμμανυελ, ficha en Pexels.bn-caballo.jpg — Caballo en blanco y negro. Foto de Ruhul Amin Akash, ficha en Pexels.ciudad-amanecer.jpg — Rascacielos de Ciudad de México al amanecer. Foto de Yuriko Salazar Luna, ficha en Pexels.ciudad-aerea.jpg — Vista aérea de una ciudad extensa. Foto de Victor Armas, ficha en Pexels.