Grid: filas y columnas
En resumen: CSS Grid coloca los elementos a la vez en filas y columnas: columnas con fr y gap, un catálogo con repeat(auto-fill, minmax()) y una disposición de página con grid-template-areas.
Problema
El último producto es enorme
El catálogo usa flex-wrap de la lección anterior. Cuatro productos: tres caben en la primera línea, y el cuarto está solo en la segunda línea y es tan ancho como toda la línea.
Consola
Esto no es un error de flexbox. Cada línea de un contenedor flex reparte su propio espacio libre y no sabe nada de la línea de arriba. La segunda línea tiene un producto, así que toma todo: 768px. Necesitamos algo que dibuje las columnas una sola vez para todo el catálogo.
La idea
Filas y columnas
Grid es una disposición en dos dimensiones: el contenedor dibuja columnas y filas, y los elementos van a las celdas uno a uno, fila por fila.
.catalog { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 24px;}display: gridconvierte el elemento en un contenedor grid, y sus hijos son elementos grid.grid-template-columnsdice cuántas columnas hay y cuánto mide cada una:200px 1fres una columna de 200px y otra que toma el resto.fres una parte del espacio libre, una vez quitadas las columnas de tamaño fijo y los gap.1fr 2fr 1fres 1 : 2 : 1.repeat(3, 1fr)es lo mismo que1fr 1fr 1fr.gapes el espacio entre las columnas y entre las filas.
La diferencia con flexbox: con flex los elementos deciden su ancho, y cada línea va por separado. Con grid el contenedor decide las columnas, y los elementos solo las llenan. Por eso el último producto se queda en su columna.
Pruébalo
Tres columnas
Haz del catálogo un grid con tres columnas iguales y un gap de 24px. El catálogo llega hasta 720px, así que cada tarjeta, también la última, tiene que medir (720 − 2 × 24) : 3 = 224px.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Catálogo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Catálogo</h1>
<div class="catalog">
<article class="card">
<img src="img/bread.svg" alt="" width="48" height="48">
<h2>Pan blanco</h2>
<p>1,20 €</p>
</article>
<article class="card">
<img src="img/bread.svg" alt="" width="48" height="48">
<h2>Pan integral</h2>
<p>1,90 €</p>
</article>
<article class="card">
<img src="img/milk.svg" alt="" width="48" height="48">
<h2>Leche fresca</h2>
<p>1,60 €</p>
</article>
<article class="card">
<img src="img/milk.svg" alt="" width="48" height="48">
<h2>Yogur</h2>
<p>1,10 €</p>
</article>
<article class="card">
<img src="img/cheese.svg" alt="" width="48" height="48">
<h2>Queso blanco</h2>
<p>7,90 €</p>
</article>
</div>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.catalog {
max-width: 720px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24px;
}
.card {
flex: 1 1 200px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card h2 { margin: 8px 0 4px; font-size: 1rem; }
.card p { margin: 0; }
display: grid y grid-template-columns: 1fr 1fr 1fr (o repeat(3, 1fr)). gap: 24px ya está, y con grid se aplica entre las columnas y entre las filas. flex: 1 1 200px en las tarjetas ya no hace nada: no son elementos flex. 1fr 2fr 1fr da tres columnas, pero la del medio es el doble de ancha.
La idea
Tantas como quepan
Tres columnas están bien en una computadora. En un teléfono miden 100px cada una y el producto no se puede leer. No queremos dar el número, sino una regla: «columnas de al menos 200px, tantas como quepan».
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));minmax(200px, 1fr): la columna mide al menos 200px y como máximo una parte.auto-fillen lugar de un número: el navegador pone tantas columnas del ancho mínimo como quepan, contando los gap.- Después el resto se reparte por igual (
1fr), y las columnas crecen hasta el final de la fila.
Míralo
Mira cómo se cuentan
La misma regla en un catálogo de 960, 680 y 328px.
.catalog { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;}Paso a paso, en palabras
- El catálogo es un grid. Las columnas no se dan con un número, sino con una regla: tantas como quepan, cada una de al menos 200px.
- Un catálogo de 960px. El navegador coloca columnas de 200px con un gap de 16px mientras quepan: 4 × 200 + 3 × 16 = 848px. Una quinta llegaría a 1064px: no cabe.
- Sobran 960 − 848 = 112px. 1fr los reparte por igual: cada columna mide (960 − 3 × 16) : 4 = 228px.
- Con 680px caben tres columnas (3 × 200 + 2 × 16 = 632). Una cuarta necesitaría 848. Cada una mide (680 − 32) : 3 = 216px.
- Con 328px, un teléfono, cabe una: dos necesitarían 416px. Ocupa todo el ancho: 328px.
- Una regla, tres disposiciones. El número de columnas sigue el ancho del catálogo, sin media queries. Los productos llenan las columnas fila por fila.
El mismo catálogo como página, primero en un teléfono (360px) y luego en una computadora (1000px). El código es el mismo.
Consola
Consola
Las columnas dependen del ancho del catálogo, no del de la pantalla. Un catálogo en una columna estrecha de la página tiene menos columnas, incluso en una pantalla grande.
Pruébalo
Una regla para los dos sitios
La página tiene un catálogo y una columna estrecha «También compraron» (220px). Los dos son .catalog con flex-wrap, y el último producto del catálogo es demasiado ancho. Cambia la regla de .catalog: grid, columnas de al menos 200px, tantas como quepan, estiradas hasta el final. La comprobación espera 3 columnas en el catálogo, cada una de 208px, y 1 columna de 220px a la derecha.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Catálogo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="page">
<main>
<h1>Catálogo</h1>
<div class="catalog">
<article class="card">
<img src="img/bread.svg" alt="" width="48" height="48">
<h2>Pan blanco</h2>
<p>1,20 €</p>
</article>
<article class="card">
<img src="img/bread.svg" alt="" width="48" height="48">
<h2>Pan integral</h2>
<p>1,90 €</p>
</article>
<article class="card">
<img src="img/milk.svg" alt="" width="48" height="48">
<h2>Leche fresca</h2>
<p>1,60 €</p>
</article>
<article class="card">
<img src="img/milk.svg" alt="" width="48" height="48">
<h2>Yogur</h2>
<p>1,10 €</p>
</article>
<article class="card">
<img src="img/cheese.svg" alt="" width="48" height="48">
<h2>Queso blanco</h2>
<p>7,90 €</p>
</article>
</div>
</main>
<aside>
<h2>También compraron</h2>
<div class="catalog">
<article class="card">
<img src="img/product.svg" alt="" width="48" height="48">
<h2>Mantequilla</h2>
<p>3,20 €</p>
</article>
<article class="card">
<img src="img/tomatoes.svg" alt="" width="48" height="48">
<h2>Tomates</h2>
<p>3,60 €</p>
</article>
</div>
</aside>
</div>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.page {
max-width: 900px;
display: grid;
grid-template-columns: 1fr 220px;
gap: 24px;
}
aside h2 { margin: 0 0 12px; font-size: 1.1rem; }
.catalog {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
}
.card {
flex: 1 1 200px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card h2 { margin: 8px 0 4px; font-size: 1rem; }
.card p { margin: 0; }
display: grid y grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)). El catálogo mide 900 − 220 − 24 = 656px: caben tres columnas (632px), y lo que sobra las deja de 208px cada una. En la columna de 220px cabe una, y mide 220px. repeat(3, 1fr) hace tres columnas también en la columna estrecha, donde las tarjetas quedan apretadas. Con repeat(auto-fill, 200px) las columnas no crecen, y quedan 20px vacíos a la derecha.
La idea
Áreas de la página
Grid también coloca la propia página: la cabecera, los filtros a la izquierda, el catálogo, el pie. Las áreas se pueden dibujar con nombres:
.page { display: grid; grid-template-columns: 220px 1fr; grid-template-areas: "header header" "filters main" "footer footer"; gap: 16px;}header { grid-area: header; }.filters { grid-area: filters; }- Cada línea entre comillas es una fila del grid, y cada palabra es una celda. Una palabra en dos celdas contiguas forma un área que ocupa las dos: la cabecera abarca dos columnas.
grid-area: headercoloca el elemento en el área con ese nombre.- Cada fila tiene que tener tantas celdas como columnas hay, y un área tiene que ser un rectángulo.
Sin nombres, un elemento se coloca por los números de las líneas entre las columnas: grid-column: 1 / 3 va de la primera a la tercera línea (dos columnas), y grid-column: 1 / -1 llega hasta la última línea. grid-column: span 2 ocupa dos columnas allí donde el navegador lo ponga.
Pruébalo
La página
Coloca la página (900px) con grid-template-areas: arriba la cabecera sobre todo el ancho, debajo los filtros a la izquierda (220px) y el catálogo a la derecha, y abajo el pie sobre todo el ancho. El gap es de 16px.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Catálogo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="page">
<header>
<a href="/">La tienda</a>
</header>
<aside class="filters">
<h2>Filtros</h2>
<label><input type="checkbox"> Solo disponibles</label>
<label><input type="checkbox"> Hasta 5 €</label>
</aside>
<main>
<h1>Catálogo</h1>
<p>Pan blanco, Pan integral, Leche fresca, Yogur, Queso blanco, Manzanas</p>
</main>
<footer>Entrega en 2 días laborables</footer>
</div>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.page {
max-width: 900px;
display: grid;
grid-template-columns: 220px 1fr;
grid-template-areas:
"header header"
"filters main"
"footer footer";
gap: 16px;
}
header { grid-area: header; }
.filters { grid-area: filters; }
main { grid-area: main; }
footer { grid-area: footer; }
header, footer { padding: 12px 16px; background: #f3efe6; }
.filters { padding: 12px; border: 1px solid #ddd; border-radius: 8px; }
.filters h2 { margin: 0 0 8px; font-size: 1.1rem; }
.filters label { display: block; }
Dos columnas, 220px 1fr, y tres filas de áreas: "header header", "filters main", "footer footer". Cada uno de los cuatro elementos recibe un grid-area con su nombre. El catálogo mide 900 − 220 − 16 = 664px. Un elemento sin grid-area no está en ningún área: el navegador lo pone solo en la primera celda libre. La cabecera sin grid-area va arriba a la izquierda y mide 220px.
Pruébalo
Encuentra el error
La página se ha desmontado: la cabecera, los filtros, el catálogo y el pie están unos encima de otros. La consola no muestra ningún error. ¿Qué línea es la causa?
Elige la línea con el error.
La línea 7 tiene una celda, y hay dos columnas. Un valor así no es válido, y el navegador se salta todo el grid-template-areas, no solo esa línea. Entonces no existen las áreas header, filters, main y footer, y los cuatro elementos con grid-area van al mismo sitio fuera de las columnas, unos encima de otros. Debería ser "footer footer". En las herramientas de desarrollo (F12) la propiedad aparece tachada.
Más precisamente, para curiosos
Filas implícitas. grid-template-columns y grid-template-rows describen el grid explícito. Si hay más elementos, el navegador añade filas por su cuenta. Son implícitas y valen auto, tan altas como el contenido. Su altura se define con grid-auto-rows.
auto-fill y auto-fit. La diferencia se ve cuando hay menos elementos que columnas. En un catálogo de 960px con dos productos, auto-fill mantiene las cuatro columnas, y los productos miden 228px cada uno. auto-fit elimina las columnas vacías, y los dos productos pasan a medir 472px cada uno.
fr y el contenido. 1fr es minmax(auto, 1fr): la columna no se hace más estrecha que la palabra más larga que contiene. Si una palabra larga estira la columna, minmax(0, 1fr) la frena.
Grid o flex. Flexbox es para un eje: una cabecera, una fila de botones, una línea del carrito. Grid es para dos: un catálogo, una página, un panel. A menudo van juntos: la página es un grid, y la cabecera de dentro es flex.
Más. grid-auto-flow: dense rellena las celdas vacías con elementos posteriores, pero altera el orden en la pantalla respecto al árbol. subgrid deja que un grid anidado use las columnas de su padre.
Ejercicio
El producto de la semana
Haz del catálogo (900px) un grid: columnas de al menos 200px, tantas como quepan, gap de 16px. El producto de la semana va primero y ocupa dos columnas. Todas las filas miden 160px de alto, también las que añada el navegador por su cuenta. La comprobación espera 4 columnas de 213px, el producto de la semana de 442px de ancho y el último producto de 160px de alto.
Consola
Solución
index.html<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Catálogo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Catálogo</h1>
<div class="catalog">
<article class="card featured">
<p class="badge">Producto de la semana</p>
<h2>Queso kashkaval</h2>
<p>9,50 €</p>
</article>
<article class="card">
<img src="img/bread.svg" alt="" width="48" height="48">
<h2>Pan blanco</h2>
<p>1,20 €</p>
</article>
<article class="card">
<img src="img/bread.svg" alt="" width="48" height="48">
<h2>Pan integral</h2>
<p>1,90 €</p>
</article>
<article class="card">
<img src="img/milk.svg" alt="" width="48" height="48">
<h2>Leche fresca</h2>
<p>1,60 €</p>
</article>
<article class="card">
<img src="img/milk.svg" alt="" width="48" height="48">
<h2>Yogur</h2>
<p>1,10 €</p>
</article>
<article class="card">
<img src="img/cheese.svg" alt="" width="48" height="48">
<h2>Queso blanco</h2>
<p>7,90 €</p>
</article>
<article class="card">
<img src="img/apples.svg" alt="" width="48" height="48">
<h2>Manzanas</h2>
<p>2,10 €</p>
</article>
</div>
</body>
</html>
style.css* { box-sizing: border-box; }
body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; }
h1 { margin: 0 0 12px; font-size: 1.4rem; }
.catalog {
max-width: 900px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: 160px;
gap: 16px;
}
.card {
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card h2 { margin: 8px 0 4px; font-size: 1rem; }
.card p { margin: 0; }
.featured {
grid-column: span 2;
background: #fff4e0;
}
.badge { color: #c2531a; font-weight: 700; }
.catalog { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-auto-rows: 160px; gap: 16px;}.featured { grid-column: span 2; }Cuatro columnas de (900 − 48) : 4 = 213px. El producto de la semana mide 2 × 213 + 16 = 442px, porque también ocupa el gap entre sus dos columnas. Él y seis productos ocupan ocho celdas: dos filas. grid-template-rows: 160px da 160px solo a la primera fila, y la segunda es implícita y tan alta como su contenido.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: El documento HTML · Selectores y la cascada · El modelo de caja · Flexbox: fila y columna
- Continúa en: Diseño responsive · El escaparate de la tienda
- Ejercicios sobre el tema: CSS: el aspecto de la página