CSS Grid Explained: Rows, Columns, Areas, and When to Reach for Grid Instead of Flexbox
2026-08-17
You’ve seen display: grid and grid-template-columns: repeat(3, 1fr), but the first time you try to build a real page layout, the pieces don’t snap together the way you expected. Grid is the most powerful layout system CSS has ever shipped — and the most different from everything before it. Once you internalize a few mental models — tracks, lines, areas, and the implicit grid — the rest is detail you can look up. This guide builds those models from the ground up.
Grid Is Two-Dimensional
This one sentence is the whole reason grid exists: flexbox lays out along one axis at a time; grid defines rows and columns up front. A flex container figures out its layout as items arrive; a grid container is a fixed table of cells into which items get placed. Everything else in this guide is a consequence of that difference — including the decision of which one to use (the short version: page skeletons and card grids → grid; distributing a group along a line → flexbox).
Tracks: Rows and Columns
A grid’s rows and columns are its tracks, created by two declarations:
display: grid;
grid-template-columns: 200px 1fr 1fr; /* three column tracks */
grid-template-rows: 100px auto; /* two row tracks */
Here repeat(3, 1fr) would do the same as three 1fr values. Track sizes can be fixed lengths (200px, 4rem), auto (content-sized), percentages, or fr fractions — which deserve their own section.
The lines that separate and surround tracks are grid lines, numbered starting at 1: three columns produce four vertical lines. You’ll use line numbers when placing items.
The fr Unit and How Track Sizing Works
fr is a fraction of the leftover space — not a length, not a percentage. This is the single most-misunderstood grid value:
- The browser first places the fixed and
autotracks. - The space that remains is split among the
frtracks in proportion to their numbers.
So 1fr 1fr 1fr is three equal shares of what’s left after the fixed tracks, and 1fr 1fr 2fr gives the third column twice the share of the others. Two consequences worth internalizing:
1fris not equal to1pxor any fixed width — it’s “one share of the leftovers.”frtracks won’t overflow the container the way100%tracks do.grid-template-columns: 1fr 1frwith one content-heavy track can shrink gracefully;50% 50%plus a large gap or wide content can blow past the edge.
minmax() adds a floor and ceiling to a track: minmax(200px, 1fr) means “at least 200px, then grow to fill the rest” — the building block of responsive layouts below.
Placing Items: Lines, Spans, and Areas
Grid items are placed auto-matically into cells, row by row, in source order — many layouts need nothing more. When you want control, you place items against line numbers:
grid-column: 1 / 3; /* start at line 1, end at line 3 → spans 2 columns */
grid-row: 1 / 2;
The value is start / end line numbers. Because three columns have four lines, 1 / 3 means “columns one and two.” The span syntax is friendlier when you don’t want to count: grid-column: span 2 means “take up two columns from wherever you’d land.” 1 / -1 is a useful idiom meaning “from the start to the last line” — a header spanning the full width.
For anything more complex, named areas are the readable option.
grid-template-areas: Layout in ASCII
Named areas let you describe the whole page structure in a single, ASCII-looking declaration:
display: grid;
grid-template-columns: 200px 1fr;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
Each child then claims a slot with one line: grid-area: header; (or sidebar, main, footer). The layout is readable at a glance, and rearranging for a mobile breakpoint is as easy as re-templating the areas. Two rules: every area must be rectangular (an L-shape is invalid), and each cell of the template must be filled — use . for empty cells.
Gaps and Alignment
Gaps. gap (with row-gap / column-gap) adds spacing between tracks. It never adds space at the container’s edges.
Aligning items within cells. justify-items (horizontal) and align-items (vertical) position an item inside its own cell; stretch is the default, which is why grid items fill their cells. justify-self / align-self override a single item.
Aligning the grid within the container. When the tracks are smaller than the container — common with auto content rows — justify-content and align-content position the whole track grid (values like center, space-between, space-evenly). If a grid seems to leave unexplained blank space, the tracks aren’t filling the container, and one of these is the lever.
The Implicit Grid and the Responsive Trick
Place items beyond your declared tracks and grid creates new tracks on the fly — the implicit grid. Its default size is auto (content-sized), and you can control it: grid-auto-rows: 100px makes every auto-created row 100px tall, and grid-auto-flow: dense lets later items fill holes left by earlier ones.
The trick that powers endless responsive layouts:
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
Reading it: “as many columns as fit, each between 200px and one fraction of the row; collapse empty ones.” One declaration gives you a card grid that flows from three columns on a wide screen down to one on a phone — no media queries. The nuance is auto-fit vs auto-fill: auto-fit collapses tracks with no items (so the row always looks full), while auto-fill keeps the empty tracks and can leave blank space. For card grids, you almost always want auto-fit.
Grid vs Flexbox: The Decision
- Grid — when the layout has rows and columns that must agree: page skeletons, card grids, dashboards, anything where you can picture the cell table before the content arrives.
- Flexbox — when you’re distributing or aligning a group along one axis: nav bars, toolbars, rows of buttons, an icon next to two lines of text.
They’re not rivals — they compose. The common pattern is grid for the page skeleton, flexbox inside each cell for the fine-grained alignment. If you’re still deciding, the CSS Flexbox Explained guide covers the one-dimensional side of the decision.
Quick Reference
- Grid defines rows and columns up front; flexbox lays out one axis at a time.
fr= a share of leftover space, not a length — fixed/autotracks are sized first,frsplits the rest.minmax(200px, 1fr)= “at least 200px, then grow.”grid-column: 1 / 3places an item between line 1 and line 3;span 2and1 / -1are handy variants.grid-template-areaslays a page out in ASCII; areas must be rectangles,.is an empty cell.justify-items/align-itemsplace items in their cells (defaultstretch);justify-content/align-contentplace the track grid in the container.repeat(auto-fit, minmax(200px, 1fr))= responsive card grids with no media queries.- Two dimensions → grid; one line of items → flexbox.
To design a grid visually, the CSS Grid Generator lets you drag out rows, columns, and areas and copies the finished CSS — and the CSS Flexbox Generator covers the flexbox half of the layout toolbox.