CSS Grid fundamentals — two-dimensional layout
Grid solves the problem flexbox structurally can't — rows and columns that align together as one shared layout — by letting you define the whole layout's structure up front, then place items into it.
3 min read
The problem flexbox can't solve, that grid was built for
Flexbox, one row at a time — nothing forces columns in DIFFERENT rows
to line up with each other, because each flex row only knows about
its own items:
[ Item A ][ Item BB ][ Item C ]
[ Item DDDD ][ Item E ]
Grid — a single shared structure across the WHOLE layout, so columns
genuinely align across every row:
[ Item A ][ Item BB ][ Item C ]
[ Item D ][ Item E ][ ]
Flexbox is fundamentally one row (or column) reasoning about its own items independently — nothing connects the column widths of one flex row to the column widths of the row below it, because flexbox has no concept of "row below it" at all. Grid defines the entire two-dimensional structure — every row and column — as one shared layout up front, so content in different rows genuinely lines up in shared columns. This is the concrete, structural reason grid exists as a separate tool rather than flexbox just growing a "2D mode."
Defining the structure: grid-template-columns and grid-template-rows
.layout {
display: grid;
grid-template-columns: 200px 1fr 1fr; /* 3 columns: fixed, then two equal flexible ones */
grid-template-rows: auto 1fr auto; /* header/content/footer, content takes remaining space */
gap: 16px;
}fr is grid's own unit — a fraction of the leftover space after fixed-size tracks (200px here) are subtracted, directly analogous to flex-grow's ratio behavior in the previous lesson, just applied to a whole grid's worth of tracks at once instead of individual flex items. Children of .layout are automatically placed into this grid in source order, one per cell, without any placement rules needed — grid works with zero extra CSS on the children for the simple case.
grid-template-areas: naming the layout instead of counting tracks
.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-areas:
"sidebar header"
"sidebar main";
}
.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
.main { grid-area: main; }Named areas let a layout's actual shape be read directly out of the CSS as a small ASCII picture, rather than reconstructed mentally from column/row line numbers — .sidebar spanning two rows here is visible directly in the template's shape (sidebar appears in both rows), not something that requires separately counting grid-row: 1 / 3. This is a real readability win specifically for page-level layouts with a fixed, named set of regions (header, sidebar, main, footer) — less useful for a dynamic grid of many similar items, where repeat() (below) fits better.
repeat() and auto-fit: a responsive grid without a single media query
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}repeat(auto-fit, minmax(200px, 1fr)) tells the browser to fit as many 200px-minimum, evenly-flexible columns as will comfortably fit in the available width — recalculating the actual column count automatically as the container resizes, with zero media queries written. This single line replaces what used to require several explicit breakpoints in older CSS, and it's one of the clearest examples of grid doing something flexbox genuinely cannot: reasoning about how many same-sized columns fit, as a first-class layout question.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What can grid do that flexbox structurally cannot?
2. What does the `fr` unit represent in `grid-template-columns: 200px 1fr 1fr`?
3. What does `grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))` achieve without any media queries?