When flex is not enough
Flexbox lays things out along one line (that may wrap). Grid lays things out on a two-dimensional grid — rows and columns at once. The moment you think "3 columns of cards" or "sidebar + content" or "photo gallery", you want grid.
| Flexbox | Grid | |
|---|---|---|
| Dimensions | 1D (a row or a column) | 2D (rows × columns) |
| Sizing driven by | the content | the layout you declare |
| Best for | navbars, toolbars, centering, rows | card grids, dashboards, galleries, page shells |
Simple honest rule: content-out → flex, layout-in → grid. And they nest freely — a grid of cards where each card is internally flex is the standard combo.
The 3-class card grid
<div class="grid grid-cols-3 gap-6">
<div>Card</div>
<div>Card</div>
<div>Card</div>
<div>Card</div> <!-- wraps to row 2 automatically -->
</div>
grid-cols-3 creates three equal columns (1fr each). Children fill the grid left-to-right, top-to-bottom — no classes needed on them at all. That is grid's magic: the parent declares the layout, children just flow in.
Spanning rows and columns
Make one item bigger than its neighbours:
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2">Wide feature card</div>
<div>Normal</div>
<div>Normal</div>
<div class="col-span-2 row-span-2">Big hero tile</div>
<div>Normal</div>
</div>
col-span-2 = "occupy 2 column tracks". row-span-2 = "occupy 2 row tracks". This is exactly how bento grids (the Apple-style mixed-size tile layouts) are built — the editor snippet below is one.
Explicit placement
col-start-2/col-end-4— pin an item to specific grid linesgrid-rows-3— declare row tracks explicitlygrid-flow-col— fill top-to-bottom columns instead of rowsplace-items-center— shorthand centering inside each cell (items-center+justify-items-center)
The auto-responsive gallery (no breakpoints!)
The single most useful arbitrary-value pattern in Tailwind:
<div class="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-4">
<!-- as many cards as you want -->
</div>
Read it as: make as many columns as fit, each at least 200px, stretching equally. Shrink the window — the column count adjusts by itself, no md:/lg: needed. (You will still learn the breakpoint way in the next lesson, because sometimes you want exact control.)
Page shells with fixed + fluid tracks
Grid accepts mixed track sizes through arbitrary values:
<div class="grid grid-cols-[240px_1fr] min-h-screen">
<aside class="bg-slate-900 text-slate-200 p-4">Sidebar</aside>
<main class="p-8 bg-slate-50">Content</main>
</div>
240px_1fr = one fixed 240px column, one column taking all remaining space. (Underscores become spaces inside arbitrary values.) Add a header row with grid-rows-[auto_1fr] and you have a complete dashboard shell in two classes.
Alignment inside the grid
| Utility | Aligns |
|---|---|
justify-items-* | every item horizontally inside its cell |
items-* | every item vertically inside its cell |
justify-self- / self- | one specific item |
content-center | the whole track group when the grid has spare space |
Common beginner mistakes
- **Putting
grid-cols-on the children. Grid classes describe the parent*; children only getcol-span/row-spanwhen they need to be bigger. - Using grid for a navbar. One row of content-sized items is flexbox's home turf; grid forces you to fight track sizing.
- Fixed heights on cards. Let rows size themselves (
auto);row-span+ content should drive height, or text will overflow. - Forgetting
gap. Grid withoutgap-*looks broken; there is no default gutter.
Practice task
In the editor: (1) change the bento to 4 columns and let the hero tile span 3, (2) give the "Streak" tile row-span-2 and watch neighbours reflow, (3) replace grid-cols-3 with grid-cols-[repeat(auto-fit,minmax(140px,1fr))] and resize the preview panel to see auto-fit do its thing.