CSS Grid vs Flexbox: A Practical Decision Guide
Stop guessing which layout tool to reach for. A clear framework for choosing between Grid and Flexbox, with copy-paste patterns for the layouts you build every week.
Marcus Webb
Senior Writer
Hugo Nina
Fact Checked
Every developer who touches CSS eventually asks it: Grid or Flexbox? And the internet’s classic answer — “Grid is 2D, Flexbox is 1D” — is true but weirdly useless in the moment, like being told a hammer is for hammering. You’re staring at a card component, a nav bar, a pricing table. Which one do you write?
After building layout systems for more sites than I can remember, here’s the decision framework I actually use, followed by the concrete patterns that cover 95% of real-world layout work.
The one question that decides it
Ask yourself: who should be in charge — the content or the layout?
- If the content should push the layout around — items should take the space they need, wrap when they run out of room, and nobody needs to line up with anything in another row — that’s Flexbox. It’s a content-out tool.
- If the layout should discipline the content — things must land in defined areas, columns must align across rows, the design has a visible skeleton — that’s Grid. It’s a layout-in tool.
A navigation bar is content-out: links take their natural width, extra space goes wherever you send it. A dashboard is layout-in: the sidebar is 260px, the main area gets the rest, widgets snap to a grid. A photo gallery where every cell must be the same size regardless of the photo? Layout-in. A row of tags that wraps? Content-out.
This is the same distinction as “1D vs 2D,” but framed by intent, which is what you actually know when you start typing.
Flexbox: the distribution engine
Flexbox excels at one job: distributing items and space along a line, with graceful wrapping. The patterns you’ll use constantly:
The nav bar (space-between with groups)
.navbar {
display: flex;
align-items: center;
gap: 1rem;
}
.navbar .actions {
margin-left: auto; /* pushes actions to the far end */
display: flex;
gap: 0.5rem;
}
margin-left: auto inside a flex container is still one of the most useful lines in CSS — it eats all available space, shoving everything after it to the end.
The media object (avatar + text)
.media {
display: flex;
gap: 12px;
align-items: flex-start;
}
.media > img { flex-shrink: 0; }
.media > .body { min-width: 0; } /* lets long text truncate instead of overflowing */
That min-width: 0 fixes the single most common Flexbox bug: flex items refuse to shrink below their content size by default, so long unbroken strings (URLs, code) blow the layout open.
Vertical centering (finally trivial)
.center {
display: flex;
align-items: center;
justify-content: center;
}
Tag lists and button rows that wrap
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
Note gap — it works in Flexbox in every modern browser and eliminates the ancient negative-margin hacks entirely.
Grid: the skeleton builder
Grid earns its keep the moment two dimensions need to agree with each other.
The page shell
.layout {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"sidebar header"
"sidebar main"
"sidebar footer";
min-height: 100dvh;
}
.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
grid-template-areas is self-documenting — the CSS literally draws the layout. Rearranging the page for mobile is one media query that redraws the map.
The responsive card grid (no media queries)
The most valuable Grid pattern in existence:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
Cards get at least 280px, the browser fits as many columns as possible, and everything reflows on resize — zero breakpoints. (This exact pattern lays out the article cards on this site.)
Aligning content across rows: subgrid
The historic Grid weakness — children of children couldn’t align to the outer grid — was solved by subgrid, now supported in all modern engines. The classic use case is equal-height card internals:
.card-grid > .card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid; /* title, body, footer align across ALL cards */
}
Every card’s footer sits on the same line, no matter how long each title is. Before subgrid this required JavaScript or misery.
Overlap without position: absolute
Grid cells can stack. A hero with text over an image:
.hero {
display: grid;
}
.hero > * {
grid-area: 1 / 1; /* everything in the same cell */
}
.hero > .caption {
align-self: end;
}
They’re teammates: the composite pattern
The real answer to “Grid or Flexbox” is usually both, at different levels:
- Grid builds the macro structure: page shell, card grids, dashboard regions, form layouts with aligned labels.
- Flexbox handles the micro structure inside each region: the row of buttons in the card footer, the icon-plus-label, the nav links.
A concrete example — a card:
.card {
display: grid; /* Grid: controls vertical regions */
grid-template-rows: auto 1fr auto;
}
.card-footer {
display: flex; /* Flexbox: distributes footer items */
justify-content: space-between;
align-items: center;
}
Once you internalize “Grid for skeletons, Flex for rows,” the decision takes about a second per element.
Decision table for common components
| Component | Tool | Why |
|---|---|---|
| Navigation bar | Flexbox | One row, content-sized items, auto margins |
| Page shell (header/sidebar/main) | Grid | Two dimensions must agree |
| Card grid / gallery | Grid | Uniform tracks, auto-fill responsiveness |
| Inside a card | Grid rows + Flex footer | Pin footer, distribute actions |
| Form with aligned labels | Grid | Columns align across rows |
| Tag/chip list | Flexbox | Natural sizes, wrapping |
| Centering one thing | Either | place-items: center (Grid) is shortest |
| Table-like data | Neither | Use an actual <table> — seriously |
That last row is a real recommendation: if the content is tabular data, semantic tables beat both layout systems for accessibility and behave better than you remember.
Performance and practical footnotes
Layout choice is rarely a performance problem, but two notes matter in practice. First, deeply nested flex containers (the “div soup” pattern old frameworks encouraged) make layout recalculation more expensive and markup harder to reason about — Grid usually flattens the tree. Second, animating grid or flex properties triggers layout; animate transform instead, as we covered in the CLS section of our Core Web Vitals guide.
And if you want to see these patterns in a real codebase, the site you’re reading is a working example — the tutorial where we build a blog with Astro uses the auto-fill card grid, the composite card pattern and the Grid page shell verbatim.
Final Thoughts
Grid vs Flexbox stopped being a debate years ago; it’s a division of labor. Flexbox distributes content along a line and is unbeatable for the small stuff. Grid imposes structure in two dimensions and is unbeatable for the big stuff. The modern additions — gap everywhere, auto-fit/minmax, subgrid, dvh units — closed the gaps that used to force hacks.
So the next time your cursor hovers over display:, ask the intent question — does content drive this layout, or does layout drive this content? — write the corresponding property, and move on to problems that deserve your attention.