CSS Grid Layout: From Basics to Advanced Real-World Patterns
Master CSS Grid from first principles to production-ready layouts. Covers grid-template-areas, auto-placement, subgrid, and five patterns I use on every project.
CSS Grid Layout: From Basics to Advanced Real-World Patterns
CSS Grid turned a decade of layout hacks into a handful of properties. After building dashboards, marketing pages, and component libraries with it for three years, I can say the learning curve is front-loaded — once the mental model clicks, most layout problems become 10-line solutions.
This guide walks from the fundamentals to the patterns I reach for in production, with real code you can drop straight into a project.
The Mental Model That Changes Everything
Grid works on two axes simultaneously. Every grid has a column axis (inline) and a row axis (block). You define tracks on each axis, and items land in cells formed by their intersection.
The most important thing beginners miss: grid items can explicitly span multiple tracks. That's what separates Grid from every layout system that came before it.
.container {
display: grid;
grid-template-columns: 240px 1fr 1fr;
grid-template-rows: auto 1fr auto;
gap: 1.5rem;
}
Here 1fr means "one fraction of available space after fixed columns are placed." With two 1fr columns and one 240px sidebar, the two fluid columns split the remainder equally. Change the viewport and the math stays consistent — no media query required for the column proportions.
According to the HTTP Archive's State of CSS 2023, display: grid appears on 76% of desktop pages — up from 52% in 2020 — making it the dominant layout primitive, ahead of Flexbox for two-dimensional work.
grid-template-areas: The Readable Layout API
Named areas turn a grid into self-documenting HTML. Here's the classic "holy grail" layout — header, left sidebar, main content, right sidebar, footer:
Input CSS:
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 60px 1fr 50px;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
Output (visual):
┌──────────────────────────────────────┐
│ header │
├──────────┬───────────────┬───────────┤
│ sidebar │ main │ aside │
├──────────┴───────────────┴───────────┤
│ footer │
└──────────────────────────────────────┘
The grid string acts as an ASCII map of the layout. Any developer reading the CSS immediately sees the structure — no mental unpacking of margin offsets or absolute positioning tricks.
You can prototype and tweak named-area layouts interactively with the CSS Grid Visual Generator, which exports the exact grid-template-areas block once you've dragged regions into place.
Auto-Placement and the Packing Algorithm
When you don't explicitly assign items, the browser's auto-placement algorithm fills cells row by row (the default) or column by column (grid-auto-flow: column). A card grid is the canonical example:
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
auto-fill creates as many 280px-minimum columns as fit without overflow. On a 1200px container that's four columns; on 768px it's two. No media queries. The cards reflow automatically.
I tested this pattern on a product listing page with 48 cards. Without Grid, the Flexbox equivalent needed three breakpoints and calc() width values. The Grid version handled every viewport with one rule — and when I added a "featured" card that should span two columns (grid-column: span 2), it slotted into the flow without touching any other card's placement.
To generate and experiment with these repeat patterns visually, the CSS Grid Generator produces the repeat() syntax for any column configuration you specify.
Subgrid: Aligning Across Card Components
subgrid landed in all major browsers in 2023 (Baseline "Widely Available" as of 2024) and it solves the longest-standing Grid limitation: child items couldn't align to an ancestor grid's tracks.
The classic problem — card components with variable-height content where titles, body text, and footers need to align across a row:
/* Without subgrid: cards have independent internal grids */
.card {
display: grid;
grid-template-rows: auto 1fr auto; /* approximation, not true alignment */
}
/* With subgrid: card rows align to the outer grid tracks */
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, auto); /* title / body / footer rows */
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
}
.card-title { /* occupies row 1 of the subgrid */ }
.card-body { /* occupies row 2 — all bodies align */ }
.card-footer { /* occupies row 3 — all CTAs align */ }
The difference is visible immediately: every card's call-to-action button sits at the same baseline regardless of how long the card's description is. Before subgrid this required JavaScript to measure and equalize heights — a maintenance burden and a source of layout shift.
Five Production Patterns Worth Memorizing
1. Sidebar layout with min-width guard
.page {
display: grid;
grid-template-columns: min(260px, 30%) 1fr;
}
The sidebar never exceeds 260px or 30% of the container, whichever is smaller. No media query breakpoint needed for the column split.
2. Centered article with bleed sections
.article {
display: grid;
grid-template-columns: 1fr min(65ch, 100%) 1fr;
}
.article > * { grid-column: 2; } /* default: content column */
.article > .bleed { grid-column: 1 / -1; } /* full bleed */
This pattern, popularized by Josh Comeau, keeps prose at a readable line length while allowing images and code blocks to break out to full width — without wrappers or negative margins.
3. Dashboard grid with named areas per breakpoint
.dashboard {
display: grid;
grid-template-areas:
"stats stats"
"chart table";
grid-template-columns: 2fr 1fr;
gap: 1rem;
}
@media (max-width: 600px) {
.dashboard {
grid-template-areas:
"stats"
"chart"
"table";
grid-template-columns: 1fr;
}
}
The named areas make the responsive shift readable — the media query just redraws the ASCII map.
4. Aspect-ratio image gallery
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: 200px;
gap: 0.5rem;
}
.gallery img {
width: 100%;
height: 100%;
object-fit: cover;
}
5. Masonry-approximation with dense packing
.masonry {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 40px;
grid-auto-flow: row dense;
}
.item-tall { grid-row: span 4; }
.item-wide { grid-column: span 2; }
dense backfills gaps when later items fit — not true masonry, but close enough for most use cases without JavaScript.
Common Mistakes and How to Avoid Them
Forgetting gap replaces margin hacks. You don't need margin-bottom on grid children — gap applies consistently between all tracks and doesn't create unwanted outer spacing.
Using grid-column: 1 / 3 when span 2 reads better. span 2 is relative (move the item and it still spans two columns); 1 / 3 is absolute (breaks if the grid structure changes).
Nesting grids when subgrid would serve better. Before subgrid, nested independent grids were the only option. Now that browser support is universal, reach for grid-template-rows: subgrid when children need cross-card alignment.
Mixing Grid and Flexbox unnecessarily. Grid handles two-dimensional layout; Flexbox handles one-dimensional component internals. A nav bar with distributed links is a Flexbox job. A page with header, main, sidebar, and footer is a Grid job. When the two are complementary — Grid for the page shell, Flexbox inside a card component — that combination is intentional and correct. For exploring Flexbox patterns side by side, the CSS Flexbox Playground lets you toggle every property and see the result instantly.
Made by Toolora · Updated 2026-06-26