CSS Grid: Two-Dimensional Layout
Flexbox handles one direction at a time. Grid handles rows and columns together, as a single layout - which is what you actually want for a page skeleton: a header across the top, a sidebar down one side, a footer across the bottom, all defined at once instead of nested flex containers fighting each other.
With Flexbox, getting a sidebar to line up with a header above it and a footer below it means carefully matching widths across three separate flex containers that don't know about each other. Grid defines all three regions as one layout, so they can't drift out of alignment - they're cells in the same table-like structure, not three unrelated rows guessing at the same width.
Turning it on
}
Every direct child becomes a grid item, placed into the cells this creates - by default, in source order, left to right, top to bottom.
Defining columns and rows
grid-template-columns and grid-template-rows take a list of sizes, one per track:
}
The unit to know is fr (fraction) - it divides leftover space after fixed-size tracks are
subtracted, similar in spirit to flex-grow. 1fr 1fr 1fr is three equal columns; 200px 1fr is a
fixed sidebar plus a column that eats everything else.
repeat() avoids repeating yourself:
); /* same as 1fr 1fr 1fr */
gap puts consistent space between tracks, no margin hacks on edge items:
}
Naming the layout: grid-template-areas
The most readable way to build a page skeleton is naming regions and drawing the layout as ASCII art directly in your CSS:
}
}
}
}
}
Dashboard
Nav
Content
Footer
What just happened: grid-template-areas is a literal picture of the layout - "header" spans both
columns on row one, "sidebar" and "main" split row two, "footer" spans both columns on row three.
Each child is placed by name with grid-area, not by counting rows and columns. Read the CSS, see the
page.
💡 Key point. This is the single biggest readability win Grid has over Flexbox. Six months from
now, grid-template-areas still reads like a floor plan. Nested flex containers reconstructing the
same layout do not.
Spanning cells: grid-column and grid-row
An item can span multiple tracks with grid-column / grid-row, using span:
}
This is how a "featured" item in a grid of photos or cards gets to be visibly bigger than its neighbors while everything else still auto-places around it. You don't have to hand-place every other item either - only the spanning one needs an explicit rule. Grid's auto-placement algorithm flows the rest into whatever cells are left, in source order, exactly like it would without any spanning at all.
Build it: a responsive photo gallery
The pattern for "as many equal-width columns as fit, wrapping automatically, no media queries":
}
<!-- more images -->
What just happened: minmax(180px, 1fr) tells each column "never shrink below 180px, but grow to
fill available space." auto-fit computes how many 180px+ columns fit the container width and
generates exactly that many tracks - four on a wide screen, two on a tablet, one on a phone - with zero
@media rules. Resize the browser and watch columns appear and disappear on their own.
⚠️ Gotcha - auto-fit vs auto-fill. auto-fit collapses empty tracks to 0px and lets existing
items stretch to fill the row, which is what you want for a gallery. auto-fill keeps empty tracks at
their minimum size (visible gaps, items don't stretch) - useful only when you specifically want
placeholder-style blank columns.
Lock in the two properties before moving on:
[
{
"q": "What does grid-template-areas let you do that raw grid-template-columns/rows doesn't as directly?",
"choices": ["Add animations to grid items", "Draw the layout as named regions that read like a floor plan", "Make the grid responsive automatically", "Skip using display: grid entirely"],
"answer": 1,
"explain": "grid-template-areas names each region and lays it out as ASCII art, so the CSS visually matches the page structure."
},
{
"q": "grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)) produces what kind of layout?",
"choices": ["A fixed 3-column grid regardless of screen size", "As many 180px-minimum columns as fit the container, wrapping automatically", "A single column on all screen sizes", "A grid that requires JavaScript to resize"],
"answer": 1,
"explain": "auto-fit calculates how many minmax-sized tracks fit the available width and generates that many columns, with 1fr letting them stretch to fill the row."
},
{
"q": "What does grid-column: span 2 do to a grid item?",
"choices": ["Moves it to column 2", "Makes it occupy two columns instead of one", "Duplicates it into two items", "Hides it on screens narrower than 2 columns"],
"answer": 1,
"explain": "span 2 tells the item to stretch across two column tracks, letting it sit visibly larger than single-column siblings."
}
]
← Phase 1: Flexbox: One-Dimensional Layout · Guide overview · Phase 3: Choosing Between Them (and Combining Them) →
Check your understanding 3 questions
1. What does grid-template-areas let you do that raw grid-template-columns/rows doesn't as directly?
2. grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)) produces what kind of layout?
3. What does grid-column: span 2 do to a grid item?