CSS Grid

CSS Grid Layout es el sistema de layout bidimensional de CSS: está diseñado para controlar simultáneamente filas y columnas, permitiendo crear layouts complejos en dos dimensiones con una precisión que Flexbox no puede alcanzar. Mientras Flexbox maneja una dimensión a la vez (fila o columna), Grid te permite definir una grilla completa—cuántas columnas, cuántas filas, qué tamaño tiene cada una—y luego colocar cada elemento exactamente donde querés. Es la herramienta definitiva para page layouts, dashboards, galerías y cualquier estructura que necesite control en ambas direcciones.

La relación conceptual con Flexbox es importante: Grid es para layouts de página (macro), Flexbox es para componentes (micro). Grid define la estructura general de la página—header, sidebar, main, footer—y dentro de cada una de esas regiones, usás Flexbox para alinear los elementos internos (navbars, cards, formularios). No son mutuamente excluyentes; se complementan. Las propiedades del contenedor grid son display: grid, grid-template-columns, grid-template-rows, grid-template-areas, gap, justify-items y align-items. Las de los ítems son grid-column, grid-row, grid-area y place-self.

Propiedades del contenedor

Todo Grid empieza con display: grid aplicado al contenedor. A partir de ese momento, los hijos directos se convierten en grid items que se posicionan automáticamente en celdas de una grilla implícita. La grilla implícita tiene columnas y filas de tamaño automático basado en el contenido—pero el verdadero poder de Grid está en definir explícitamente las columnas y filas con grid-template-columns y grid-template-rows.

Experimentá con las propiedades del contenedor Grid en vivo. Cambiá los valores de grid-template-columns, gap, justify-items, align-items y grid-auto-flow. También probá los botones de span para ver cómo los items pueden ocupar múltiples celdas.

Playground — Propiedades del contenedor Grid

grid-template-columns y grid-template-rows

Estas son las propiedades fundamentales de Grid: definen cuántas columnas y filas tiene la grilla y el tamaño de cada una. Los valores pueden ser longitudes fijas (200px, 5rem), porcentajes (50%), la unidad fr (fraction, exclusiva de Grid), funciones como minmax(), repeat(), o palabras clave como auto y min-content. La combinación de estas herramientas te da un control extremadamente preciso sobre la estructura del layout.

CSS
/* Grid basico: 3 columnas fijas de 200px */
.grid-fijo {
    display: grid;
    grid-template-columns: 200px 200px 200px;
    /* Equivalente con repeat(): */
    grid-template-columns: repeat(3, 200px);
}

/* Grid con sidebar: 280px fijo + contenido flexible */
.layout-sidebar {
    display: grid;
    grid-template-columns: 280px 1fr;
    /* 1fr = una fraccion del espacio restante */
    gap: 1.5rem;
}

/* Grid de 3 columnas proporcionales */
.proporcional {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    /* La columna central es el doble de ancha que las laterales */
    /* Si el contenedor mide 600px: 150px | 300px | 150px */
}

/* ============================================
   grid-template-rows: definir las filas
   ============================================ */
.page-layout {
    display: grid;
    grid-template-columns: 250px 1fr;
    grid-template-rows: auto 1fr auto;
    /* Fila 1: header (auto = tamaño del contenido)
       Fila 2: contenido principal (1fr = ocupa todo el espacio)
       Fila 3: footer (auto = tamaño del contenido) */
    min-height: 100vh;
}

/* ============================================
   Mezcla de unidades en una misma grilla
   ============================================ */
.page-full {
    display: grid;
    grid-template-columns: 200px 1fr 300px;
    /* Sidebar izq | Contenido principal | Sidebar der */
    grid-template-rows: 64px 1fr 48px;
    /* Header    | Main               | Footer */
    min-height: 100vh;
    gap: 0;
}

La unidad fr

La unidad fr (fraction) es exclusiva de CSS Grid y representa una fracción del espacio disponible en el contenedor después de restar los tamaños fijos y los gaps. Es diferente a porcentajes porque fr distribuye el espacio libre, no el total. Si tenés grid-template-columns: 1fr 2fr 1fr, el espacio libre se divide en 4 partes: la primera y tercera columnas obtienen 1 parte cada una, la central obtiene 2. Si ademas tenés grid-template-columns: 200px 1fr 1fr, primero se reservan los 200px, y luego el espacio restante se divide en partes iguales entre los dos 1fr.

CSS
/* ============================================
   fr vs porcentajes: la diferencia clave
   ============================================ */

/* Con porcentajes: 50% + 50% + gap = overflow!
   El gap se suma a los porcentajes */
.grid-percent {
    display: grid;
    grid-template-columns: 50% 50%;
    gap: 1rem; /* PROBLEMA: 50% + 1rem + 50% > 100% */
}

/* Con fr: nunca hay overflow por el gap
   1fr 1fr se calcula DESPUES de restar el gap */
.grid-fr {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem; /* PERFECTO: las columnas se achican para dejar espacio */
}

/* Mezcla de fr y unidades fijas */
.layout-con-sidebar {
    display: grid;
    grid-template-columns: 250px 1fr 1fr;
    gap: 1rem;
    /* 250px fijos + el resto se divide en 2 fracciones iguales */
    /* En un contenedor de 1000px con 2rem de gaps:
       250px + (1000px - 250px - 2rem) / 2 = 250 + 371 = cada columna */
}

/* min-content y max-content como tamaños */
.ajustable {
    display: grid;
    grid-template-columns: min-content 1fr max-content;
    /* min-content: la columna mide lo minimo que su contenido necesita
       max-content: la columna mide lo maximo que su contenido necesita
       1fr: el espacio restante */
}

fr vs porcentajes

Usá fr en lugar de porcentajes cuando trabajes con Grid. Los porcentajes se calculan sobre el ancho total del contenedor y no tienen en cuenta el gap, lo que puede causar overflow. fr distribuye el espacio libre restante después de restar los tamaños fijos y los gaps, así que nunca produce overflow.

Funciones esenciales: repeat(), minmax(), auto-fill, auto-fit

Estas cuatro funciones son las que hacen que Grid sea verdaderamente poderoso para layouts responsivos sin media queries. repeat() evita la repetición manual de columnas, minmax() define rangos de tamaño mínimo y máximo, y auto-fill / auto-fit crean columnas automáticamente basándose en el espacio disponible. Combinadas, permiten crear grids que se adaptan a cualquier tamaño de pantalla sin una sola media query.

repeat()

repeat() es una función que repite un patrón de columnas o filas un número determinado de veces. En lugar de escribir grid-template-columns: 1fr 1fr 1fr 1fr, escribís grid-template-columns: repeat(4, 1fr). Además, repeat() acepta valores como auto-fill y auto-fit como primer argumento, lo que la convierte en la herramienta principal para grids responsivos.

minmax()

minmax(min, max) define un rango de tamaño permitido para una columna o fila. El navegador intenta que la pista tenga al menos el tamaño min y como máximo el max. Si el espacio disponible está entre ambos, la pista se ajusta. Si hay más espacio que el max, la pista no crece más allá del max. Si hay menos espacio que el min, la pista se encoge hasta el min (y si no alcanza, genera overflow). La combinación minmax(auto, 1fr) o minmax(min-content, 1fr) es especialmente útil para layouts flexibles.

CSS
/* ============================================
   repeat(): evitar repeticion manual
   ============================================ */

/* En lugar de esto: */
.grid-manual {
    grid-template-columns: 1fr 1fr 1fr 1fr;
}

/* Usá esto: */
.grid-repeat {
    grid-template-columns: repeat(4, 1fr);
}

/* Patrones alternados: 100px, 1fr, 100px, 1fr... */
.grid-alternado {
    grid-template-columns: repeat(4, 100px 1fr);
}

/* ============================================
   minmax(): rangos de tamaño
   ============================================ */

/* Columna que mide entre 200px y 500px */
.grid-rango {
    display: grid;
    grid-template-columns: minmax(200px, 500px) 1fr;
    /* Col 1: min 200px, max 500px (se ajusta al espacio)
       Col 2: el resto (1fr) */
}

/* Sidebar con ancho flexible */
.sidebar-flex {
    display: grid;
    grid-template-columns: minmax(200px, 300px) 1fr;
    /* Sidebar: entre 200 y 300px
       Contenido: todo lo que sobre */
}

/* ============================================
   PATRON REACTIVO SIN MEDIA QUERIES
   La combinación definitiva: repeat() + minmax() + auto-fill
   ============================================ */

/* Columnas automaticas de minimo 250px
   El navegador calcula cuántas caben y crea esa cantidad
   Si caben 4: 4 columnas. Si caben 2: 2 columnas.
   SIN MEDIA QUERIES! */
.grid-responsivo {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 1.5rem;
}

/* ============================================
   auto-fill vs auto-fit: la diferencia sutil
   ============================================ */

/* auto-fill: crea columnas vacías si sobra espacio
   Si entran 3 columnas de 250px pero sobra espacio para 4:
   crea 4 columnas (la 4ta queda vacía) */
.grid-autofill {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}

/* auto-fit: colapsa las columnas vacías
   Si entran 3 items y sobra espacio para 4:
   las 3 columnas existentes se ESTIRAN para llenar el espacio
   (las columnas vacías colapsan a ancho 0) */
.grid-autofit {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}

/* PRACTICO: cuando hay pocos items y queres que ocupen todo el ancho
   usa auto-fit. Cuando hay muchos items y queres que mantengan su
   tamaño, usa auto-fill. */
Función Qué hace Cuándo usarla
repeat(3, 1fr) Crea 3 columnas de 1fr cada una. Atajo para no escribir 1fr 1fr 1fr. Siempre que tengas columnas iguales repetidas.
minmax(200px, 1fr) La columna mide entre 200px (mínimo) y 1fr (máximo). Se estira si hay espacio, pero nunca menos de 200px. Para columnas con tamaño mínimo garantizado que se adaptan al espacio.
repeat(auto-fill, ...) Crea tantas columnas como quepan en el contenedor. Las columnas sobrantes quedan vacías pero ocupan espacio. Galerías y card grids donde los items mantienen su tamaño fijo.
repeat(auto-fit, ...) Igual que auto-fill, pero las columnas vacías colapsan y las existentes se estiran para llenar el espacio. Cuando querés que los items se expandan si sobra espacio (pocas cards, widgets).

auto-fill vs auto-fit con pocos items

La diferencia solo se nota cuando hay menos items que columnas disponibles. Con auto-fill, las columnas vacías mantienen su tamaño y el grid muestra espacios vacíos. Con auto-fit, las columnas vacías colapsan a 0 y los items existentes se expanden. Si siempre tenés suficientes items para llenar el grid, ambos se comportan igual.

Propiedades de los ítems

Las propiedades de los ítems Grid controlan dónde se coloca cada elemento dentro de la grilla y cuántas celdas ocupa. A diferencia de Flexbox donde los items fluyen en una dirección, en Grid los items se pueden colocar en cualquier celda específica, abarcar múltiples columnas o filas, y hasta superponerse intencionalmente. Esto da un control de posicionamiento que no existe en ningún otro sistema de layout de CSS.

grid-column y grid-row

grid-column y grid-row permiten especificar en qué líneas de la grilla se ubica un item. El valor es start / end, donde start es la línea donde comienza y end es la línea donde termina. Las líneas de Grid son numeradas empezando desde 1, y también se pueden contar desde el final con valores negativos (-1 es la última línea, -2 la penúltima, etc.). Usar span permite decir "ocupa N celdas" sin necesidad de calcular la línea exacta de fin.

CSS
.page-grid {
    display: grid;
    grid-template-columns: 200px 1fr 1fr;
    grid-template-rows: 64px 1fr 48px;
    /* Lineas de columna: 1 | 2 | 3 | 4
       Lineas de fila:    1 | 2 | 3 | 4 */
}

/* ============================================
   Posicionamiento por lineas (start / end)
   ============================================ */

/* Header: ocupa toda la primera fila
   Columna: desde la linea 1 hasta la 4 (todas)
   Fila: desde la linea 1 hasta la 2 */
.page-grid .header {
    grid-column: 1 / 4;   /* o grid-column: 1 / -1 */
    grid-row: 1 / 2;
}

/* Sidebar: primera columna, toda la altura del contenido
   Columna: 1 a 2
   Fila: 2 a 3 */
.page-grid .sidebar {
    grid-column: 1 / 2;
    grid-row: 2 / 3;
}

/* Main: ocupa las columnas 2 y 3, fila 2 */
.page-grid .main {
    grid-column: 2 / 4;   /* o grid-column: 2 / -1 */
    grid-row: 2 / 3;
}

/* Footer: toda la ultima fila */
.page-grid .footer {
    grid-column: 1 / -1;  /* -1 = ultima linea */
    grid-row: 3 / 4;
}

/* ============================================
   Shorthands de grid-column / grid-row
   ============================================ */

/* grid-column: start / end  (forma completa) */
.item { grid-column: 2 / 4; }

/* grid-column: span N  (ocupa N columnas desde donde está) */
.item-span { grid-column: span 2; }

/* grid-row: span N  (ocupa N filas) */
.item-tall { grid-row: span 3; }

/* Combinados */
.featured {
    grid-column: 1 / 3;  /* Columnas 1 y 2 */
    grid-row: 1 / 3;     /* Filas 1 y 2 */
}

/* ============================================
   Numeros negativos: contar desde el final
   ============================================ */
/*
   Lineas:  1   2   3   4
            |   |   |   |
   Negativo:-4  -3  -2  -1
*/

.full-width {
    grid-column: 1 / -1; /* Desde la primera hasta la ultima linea */
}

.last-two {
    grid-column: -3 / -1; /* Las ultimas 2 columnas */
}

grid-area y grid-template-areas

grid-template-areas es una de las características más elegantes de Grid: permite definir el layout completo del contenedor usando nombres literales en una "plantilla visual". Cada fila es un string, y los nombres repetidos indican que un item abarca múltiples celdas. Los items usan grid-area: nombre para referenciarse. Esto hace que el CSS sea auto-documentable: mirás la plantilla y entendés instantáneamente la estructura del layout.

CSS
/* ============================================
   grid-template-areas: layout visual con nombres
   ============================================ */

.page-layout {
    display: grid;
    grid-template-columns: 250px 1fr;
    grid-template-rows: 64px 1fr 48px;
    grid-template-areas:
        "header  header"
        "sidebar content"
        "footer  footer";
    /* Fila 1: header ocupa las 2 columnas
       Fila 2: sidebar a la izq, content a la der
       Fila 3: footer ocupa las 2 columnas */
    min-height: 100vh;
    gap: 0;
}

/* Cada item se asigna a un área por nombre */
.page-layout .header  { grid-area: header; }
.page-layout .sidebar { grid-area: sidebar; }
.page-layout .content { grid-area: content; }
.page-layout .footer  { grid-area: footer; }

/* ============================================
   Layout de dashboard con grid-template-areas
   ============================================ */

.dashboard {
    display: grid;
    grid-template-columns: 200px 1fr 300px;
    grid-template-rows: 60px 1fr 1fr 40px;
    grid-template-areas:
        "nav    nav    nav"
        "aside  main1  main2"
        "aside  main3  main4"
        "footer footer footer";
    min-height: 100vh;
    gap: 1rem;
}

.dashboard .top-nav   { grid-area: nav; }
.dashboard .sidebar   { grid-area: aside; }
.dashboard .widget-1  { grid-area: main1; }
.dashboard .widget-2  { grid-area: main2; }
.dashboard .widget-3  { grid-area: main3; }
.dashboard .widget-4  { grid-area: main4; }
.dashboard .footer    { grid-area: footer; }

/* ============================================
   Areas vacias con punto (.)
   ============================================ */
/* Usá un punto para dejar celdas vacias */
.gallery-layout {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-areas:
        "featured featured featured"
        "card1   card2   card3"
        ".       card4   .";
    /* La fila 3 tiene espacios vacios a los costados */
}

.gallery-layout .featured { grid-area: featured; }
.gallery-layout .c1        { grid-area: card1; }
.gallery-layout .c2        { grid-area: card2; }
.gallery-layout .c3        { grid-area: card3; }
.gallery-layout .c4        { grid-area: card4; }

place-self, justify-self y align-self

En Grid, justify-self controla la alineación horizontal de un item dentro de su celda, y align-self controla la vertical. place-self es el shorthand que combina ambos. A diferencia de Flexbox, en Grid estas propiedades se aplican a items individuales (no hay justify-items/align-items en los items, esos son del contenedor). Los valores disponibles son start, end, center y stretch (por defecto).

CSS
.grid {
    display: grid;
    grid-template-columns: repeat(3, 200px);
    gap: 1rem;
}

/* justify-self: alineacion horizontal dentro de la celda */
.grid .item-center-h {
    justify-self: center; /* Centrado horizontal en su celda */
}

.grid .item-end-h {
    justify-self: end; /* Alineado a la derecha de su celda */
}

.grid .item-stretch-h {
    justify-self: stretch; /* Por defecto: llena todo el ancho */
}

/* align-self: alineacion vertical dentro de la celda */
.grid .item-bottom {
    align-self: end; /* Pegado al fondo de su celda */
}

.grid .item-center-v {
    align-self: center; /* Centrado vertical en su celda */
}

/* place-self: shorthand (align-self justify-self) */
.grid .item-centered {
    place-self: center; /* Centrado en ambas direcciones */
}

.grid .item-bottom-right {
    place-self: end end; /* Abajo y a la derecha */
}

/* justify-items y align-items (en el contenedor)
   Afectan a TODOS los items del grid */
.grid-all-centered {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    justify-items: center;  /* Todos centrados horizontal */
    align-items: center;     /* Todos centrados vertical */
    /* Shorthand: */
    place-items: center;     /* Centra todos en ambas direcciones */
}
Propiedad Dónde se aplica Qué controla Valores comunes
justify-items Contenedor Alineación horizontal de todos los items dentro de sus celdas. start, end, center, stretch
align-items Contenedor Alineación vertical de todos los items dentro de sus celdas. start, end, center, stretch
place-items Contenedor Shorthand de align-items + justify-items. center, start end, etc.
justify-self Ítem Sobreescribe justify-items para un item específico. start, end, center, stretch
align-self Ítem Sobreescribe align-items para un item específico. start, end, center, stretch
place-self Ítem Shorthand de align-self + justify-self. center, end end, etc.

Grid implícito y auto-flow

Cuando un grid item no cabe en las filas/columnas definidas explícitamente, o cuando hay más items que celdas, el navegador crea automáticamente filas o columnas adicionales: esto es el grid implícito. Las propiedades grid-auto-rows y grid-auto-columns controlan el tamaño de estas pistas implícitas, y grid-auto-flow controla la dirección en la que se colocan los items (por filas o por columnas).

CSS
/* ============================================
   grid-auto-flow: direccion del flujo implicito
   ============================================ */

/* row (default): los items llenan columnas de izquierda a derecha
   y pasan a la siguiente fila cuando se completa */
.grid-row-flow {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-flow: row;
    /* 1 2 3
       4 5 6
       7 8 ... */
}

/* column: los items llenan filas de arriba a abajo
   y pasan a la siguiente columna cuando se completa */
.grid-col-flow {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-flow: column;
    /* 1 4 7
       2 5 8
       3 6 ... */
}

/* dense: rellena los huecos (compactado)
   Si un item no cabe, busca el primer hueco disponible
   en lugar de crear una nueva fila/columna */
.grid-dense {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    grid-auto-flow: row dense;
    /* Los items se acomodan en los huecos dejados por items
       que ocupan multiples celdas (span 2, etc.) */
}

/* ============================================
   grid-auto-rows: tamaño de las filas implicitas
   ============================================ */

/* Las filas implicitas tienen un tamaño minimo */
.grid-min-rows {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: minmax(150px, auto);
    /* Cada fila implicita mide al menos 150px */
}

/* Todas las filas del mismo tamaño (implicitas + explicitas) */
.grid-equal-rows {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: 1fr;
    /* Las filas se expanden para que todas midan lo mismo */
}

/* ============================================
   grid-auto-columns: tamaño de las columnas implicitas
   (menos comun, se usa cuando el flujo es column)
   ============================================ */
.grid-auto-cols {
    display: grid;
    grid-template-rows: repeat(3, 100px);
    grid-auto-flow: column;
    grid-auto-columns: 200px;
    /* Columnas implicitas de 200px cada una */
}

dense para evitar huecos

Cuando usás items con span (que ocupan más de una celda), es probable que queden huecos en la grilla. Agregar grid-auto-flow: row dense hace que el navegador reacomode los items más chicos en esos huecos en lugar de dejarlos vacíos. Esto es ideal para galerías de imágenes con diferentes tamaños o dashboards con widgets de varios anchos.

Patrones comunes con CSS Grid

CSS Grid brilla en layouts bidimensionales donde necesitás controlar filas y columnas al mismo tiempo. Estos son los patrones más comunes que se resuelven elegantemente con Grid, muchos de los cuales son difíciles o imposibles de lograr con Flexbox solo. La mayoría requieren pocas líneas de CSS y son inherentemente responsivos.

Page layout completo (Holy Grail)

El clásico "Holy Grail" del web design: header arriba, footer abajo, sidebar a la izquierda, contenido principal al centro. Con Grid y grid-template-areas, este layout que históricamente requería tablas, floats o hacks complejos se define en 5 líneas de CSS. El contenido principal se expande automáticamente y el footer siempre queda abajo gracias a 1fr en la fila del contenido.

CSS
/* Holy Grail Layout con Grid */
.holy-grail {
    display: grid;
    grid-template-areas:
        "header header"
        "sidebar content"
        "footer footer";
    grid-template-columns: 260px 1fr;
    grid-template-rows: auto 1fr auto;
    min-height: 100vh;
}

.holy-grail .header  { grid-area: header; }
.holy-grail .sidebar { grid-area: sidebar; }
.holy-grail .content { grid-area: content; }
.holy-grail .footer  { grid-area: footer; }

/* ============================================
   Responsive: en mobile, sidebar arriba del contenido
   Solo cambiamos el template, sin tocar el HTML
   ============================================ */
@media (max-width: 768px) {
    .holy-grail {
        grid-template-areas:
            "header"
            "content"
            "sidebar"
            "footer";
        grid-template-columns: 1fr;
    }
}

Galería de imágenes (masonry-like)

Un patrón muy popular es la galería con imágenes de diferentes alturas que se acomodan sin huecos (estilo Pinterest). CSS Grid no tiene masonry nativo aún (la propuesta masonry está en desarrollo), pero se puede simular con grid-auto-flow: dense y items con diferentes grid-row: span. No es un masonry perfecto (los items se ordenan por fila, no por columna), pero para la mayoría de casos prácticos es suficiente.

CSS
/* Galería tipo masonry con Grid */
.masonry-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    grid-auto-rows: 10px; /* Filas pequenas para granularidad */
    grid-auto-flow: dense; /* Rellena huecos */
    gap: 15px;
}

/* Items con diferentes alturas (ocupan diferentes cantidades de filas) */
.masonry-grid .item-sm  { grid-row: span 20; } /* ~200px */
.masonry-grid .item-md  { grid-row: span 30; } /* ~300px */
.masonry-grid .item-lg  { grid-row: span 40; } /* ~400px */
.masonry-grid .item-xl  { grid-row: span 55; } /* ~550px */

.masonry-grid .item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--radius-md);
}

Card grid responsivo

El patrón más común de todos: un grid de cards que se adapta al ancho de pantalla. Con repeat(auto-fit, minmax(280px, 1fr)) no necesitás ninguna media query—el navegador calcula automáticamente cuántas cards caben y las redimensiona. Es la forma más limpia de hacer un card grid responsivo.

CSS
/* Card grid responsivo sin media queries */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 1.5rem;
}

.card-grid .card {
    background: var(--surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    display: flex;
    flex-direction: column; /* Flexbox DENTRO del Grid item */
    gap: 0.75rem;
}

.card-grid .card .card-image {
    width: 100%;
    height: 180px;
    object-fit: cover;
    border-radius: var(--radius-md);
}

.card-grid .card .card-body {
    flex: 1; /* El body crece para empujar el footer hacia abajo */
}

/* ============================================
   Card destacada que ocupa 2 columnas
   ============================================ */
.card-grid .card-featured {
    grid-column: span 2; /* Ocupa 2 columnas */
}

/* En mobile (pantallas chicas), la featured ocupa 1 sola columna */
@media (max-width: 600px) {
    .card-grid .card-featured {
        grid-column: span 1;
    }
}

Dashboard con widgets

Los dashboards son el caso de uso perfecto para Grid: necesitás colocar widgets de diferentes tamaños en una grilla bidimensional, con algunos widgets que ocupan más espacio que otros. grid-template-areas hace que el layout sea fácil de leer y modificar, y en mobile simplemente cambias el template para apilar todo verticalmente.

CSS
/* Dashboard layout */
.dashboard {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: auto;
    grid-template-areas:
        "stats1  stats2  stats3  stats4"
        "chart   chart   chart   chart"
        "table   table   activity activity"
        "table   table   tasks   tasks";
    gap: 1.5rem;
}

.dashboard .stats-1    { grid-area: stats1; }
.dashboard .stats-2    { grid-area: stats2; }
.dashboard .stats-3    { grid-area: stats3; }
.dashboard .stats-4    { grid-area: stats4; }
.dashboard .chart      { grid-area: chart; }
.dashboard .table      { grid-area: table; }
.dashboard .activity   { grid-area: activity; }
.dashboard .tasks      { grid-area: tasks; }

/* Cada widget tiene estilos base */
.dashboard .widget {
    background: var(--surface);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
}

/* Responsive: en tablets, 2 columnas */
@media (max-width: 1024px) {
    .dashboard {
        grid-template-columns: repeat(2, 1fr);
        grid-template-areas:
            "stats1  stats2"
            "stats3  stats4"
            "chart   chart"
            "table   table"
            "activity tasks";
    }
}

/* En mobile, todo en 1 columna */
@media (max-width: 640px) {
    .dashboard {
        grid-template-columns: 1fr;
        grid-template-areas:
            "stats1"
            "stats2"
            "stats3"
            "stats4"
            "chart"
            "table"
            "activity"
            "tasks";
    }
}

Superposición de elementos (overlay)

Un truco poderoso de Grid es que los items pueden superponerse intencionalmente al colocarlos en las mismas celdas. Esto es útil para overlays de imágenes, tooltips posicionados sobre contenido, o elementos decorativos. Cuando los items se superponen, el que aparece despuués en el DOM se renderiza encima (salvo que uses z-index).

CSS
/* Hero con imagen de fondo y texto superpuesto */
.hero-overlay {
    display: grid;
    place-items: center; /* Centra el texto */
    min-height: 400px;
    position: relative;
}

.hero-overlay .hero-bg {
    grid-column: 1 / -1;
    grid-row: 1 / -1;
    /* Ocupa toda la grilla (background) */
    z-index: 0;
}

.hero-overlay .hero-content {
    grid-column: 1 / -1;
    grid-row: 1 / -1;
    /* Misma celda que el bg, pero encima */
    z-index: 1;
    text-align: center;
    color: white;
}

/* ============================================
   Badge superpuesto en una card
   ============================================ */
.card-with-badge {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto 1fr auto;
}

.card-with-badge .badge {
    /* Se superpone sobre la esquina superior derecha */
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    z-index: 1;
    transform: translate(0.5rem, -0.5rem);
}

Grid vs Flexbox: cuándo usar cada uno

Una de las preguntas más frecuentes es: "uso Grid o Flexbox?". La respuesta corta es que no son mutuamente excluyentes—se usan juntos. Grid controla la estructura bidimensional del layout de la página (qué va en cada columna y fila), y Flexbox controla la alineación dentro de cada componente (items de una navbar, contenido de una card, botones de un formulario). Pensá en Grid como el plano arquitectónico de la casa y Flexbox como el diseño interior de cada habitación.

La regla general es: si necesitás controlar filas y columnas al mismo tiempo, usá Grid. Si necesitás alinear elementos en una sola dirección (una fila de botones, una columna de items de menú), usá Flexbox. En la práctica, la mayoría de las páginas usan Grid para el layout principal y Flexbox para los componentes internos, y es perfectamente normal anidar Flexbox dentro de Grid items y viceversa.

Criterio Flexbox CSS Grid
Dirección Unidimensional (fila o columna) Bidimensional (filas y columnas simultáneamente)
Mejor para Componentes internos: navbars, barras de herramientas, cards, formularios, filas de botones. Layout de página: page layouts, dashboards, galerías, grillas bidimensionales.
Control del tamaño flex-grow/shrink/basis para crecimiento proporcional. grid-template-columns/rows, fr, minmax() para definición explícita de pistas.
Posicionamiento Secuencial (orden del DOM), order para cambiar visualmente. Arbitrario: cada item puede ir en cualquier celda, con grid-column/row y grid-area.
Alineación justify-content (eje principal), align-items (eje transversal). justify-items, align-items, justify-content, align-content.
Responsivo flex-wrap: wrap + flex: 1 1 Npx para wrapping básico. repeat(auto-fill/auto-fit, minmax(Npx, 1fr)) para grids responsivos sin media queries.
Contenido dinámico Mejor para items de tamaño variable en una sola dirección. Mejor cuando el tamaño y posición de cada item es predecible.
Curva de aprendizaje Más sencilla: pocas propiedades, una dimensión. Más compleja: más propiedades, dos dimensiones, líneas numeradas.
CSS
/* ============================================
   EJEMPLO REAL: Grid + Flexbox combinados
   Grid para la estructura, Flexbox para los componentes
   ============================================ */

/* GRID: Layout principal de la página */
.app-layout {
    display: grid;
    grid-template-areas:
        "header header"
        "sidebar content"
        "footer footer";
    grid-template-columns: 260px 1fr;
    grid-template-rows: auto 1fr auto;
    min-height: 100vh;
}

/* FLEXBOX: Dentro del header (navbar) */
.app-layout .header {
    grid-area: header;
    display: flex;           /* Flexbox para alinear items del header */
    align-items: center;
    justify-content: space-between;
    padding: 0 1.5rem;
    height: 60px;
}

.app-layout .header .logo {
    flex-shrink: 0; /* Logo nunca se encoge */
}

.app-layout .header .nav-links {
    display: flex;           /* Flexbox para los links */
    gap: 1rem;
    list-style: none;
}

/* FLEXBOX: Dentro del sidebar */
.app-layout .sidebar {
    grid-area: sidebar;
    display: flex;           /* Flexbox para el menu lateral */
    flex-direction: column;
    gap: 0.25rem;
    padding: 1rem;
}

/* FLEXBOX: Dentro de cada card del contenido */
.app-layout .content {
    grid-area: content;
    display: grid;           /* Grid para las cards */
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1.5rem;
    padding: 1.5rem;
}

/* Cada card usa Flexbox internamente */
.app-layout .content .card {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    background: var(--surface);
    border-radius: var(--radius-lg);
    padding: 1.25rem;
}

.app-layout .content .card .card-actions {
    display: flex;           /* Flexbox para los botones de la card */
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: auto;       /* Empuja los botones al fondo */
}

/* GRID: Footer */
.app-layout .footer {
    grid-area: footer;
    display: flex;           /* Flexbox para el contenido del footer */
    align-items: center;
    justify-content: space-between;
    padding: 0 1.5rem;
    height: 48px;
}

La regla del 80/20

Si el 80% del tiempo estás alineando elementos en una sola dirección, Flexbox es suficiente. Si necesitás controlar explícitamente dónde va cada cosa en dos dimensiones (layout de página, dashboard, galería con items de distinto tamaño), Grid es la herramienta correcta. En la práctica: Grid para la página, Flexbox para los componentes.

Probá en MiniDevTools

Si querés experimentar con lo que vimos en esta sección, probá el Grid Playground.