Flexbox vs Grid
Quick answer: Use grid when you know the shape of the layout before you know the content — rows and columns that must line up across both axes. Use flexbox when the content decides the shape — a row of items that size themselves and wrap when they run out of room. One axis of alignment means flexbox; two means grid.
Both shipped years ago, both are Baseline widely available, and neither replaced the other. The difficulty is not learning them — it is knowing which one a given piece of UI wants, and the rule most people carry around gives the wrong answer often enough to matter.
The rule you were taught is wrong
“Grid is for page layout, flexbox is for components” is not the distinction. It was a useful simplification in 2017 when grid was new and people needed permission to use it for the big boxes. As a decision rule it fails constantly in both directions:
- A card component whose image, title, body and footer must align with the cards beside it is a grid problem, not a flexbox one — the alignment crosses component boundaries.
- A page-level toolbar with a logo, some links and a button pushed to the end is a flexbox problem, even though it is page furniture.
The real distinction is about where the sizes come from:
| Flexbox | Grid | |
|---|---|---|
| Direction | Content-out — items size themselves, the container accommodates | Layout-in — you define tracks, items fill them |
| Axes | One at a time. Wrapping creates independent lines | Two at once. Rows and columns are a single structure |
| Alignment across lines | No — each wrapped line sizes independently | Yes — that is the entire point of tracks |
| Overlap | Not without negative margins or absolute positioning | Native — two items can share a cell |
| Gaps in the structure | Items are consecutive | Cells can be deliberately empty |
| Best at | Distributing space in one direction | Holding a shape regardless of content |
The one-sentence test: if moving one item should affect the position of an item in a different row, you need grid. Flexbox lines do not know about each other.
Answer three questions
Which one do I need?
Three questions about the layout you are building. The verdict updates as you answer, with starter CSS you can paste.
Nothing selected yet — pick an option in each row.
/* your starter CSS appears here */
The two flexbox defaults that cause most bugs
flex: 1 is not flex-grow: 1
This is the most consequential misunderstanding in flexbox, and it is a one-line difference in the specification. The flex shorthand sets three properties, and when you give it a single number, the basis is zero:
flex: 1; /* → flex: 1 1 0% */
flex: auto; /* → flex: 1 1 auto */
flex: none; /* → flex: 0 0 auto */
That 0% versus auto is the whole behaviour:
flex: 1— every item starts from zero width, then the free space is divided equally. Result: equal-width columns, regardless of content.flex: auto— every item starts at its content width, then the leftover space is divided equally. Result: columns proportional to their content.
So when someone reports that their three columns are not equal despite flex-grow: 1 on each, the answer is almost always that they wrote flex-grow: 1 — leaving flex-basis at its initial auto — instead of flex: 1. The two are not interchangeable.
Flex items refuse to shrink below their content
A flex item’s min-width computes to auto, not 0. Automatic minimum size means the item will not shrink below the width of its content — so a long unbroken string, a wide table or a <pre> block pushes the item past its share and out of the container. It looks like overflow: hidden is broken. It is not:
.item {
min-width: 0; /* row direction */
/* min-height: 0; for column direction */
}
Or, on the element whose text is doing the pushing, overflow: hidden — which also sets the automatic minimum size to zero. Grid has the same rule with the same fix, and there the idiom is to write tracks as minmax(0, 1fr) rather than 1fr, since 1fr is shorthand for minmax(auto, 1fr).
min-width: 0 on flex items that contain arbitrary text, and minmax(0, 1fr) instead of 1fr in grid track definitions. Both undo the same automatic-minimum-size rule.Trying to work out how a site built a layout? CSS DNA shows the display mode, track definitions, flex values and computed box model for any element you hover — so you can read a grid’s columns without reconstructing them from DevTools. Add it free.
Layouts where the choice is obvious
Grid: the responsive card wall with no media queries
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 20px;
}
Cards are at least 260px, as many per row as fit, all equal width, all rows aligned. There is no flexbox equivalent — with flex-wrap the last row stretches its items to fill, which is why flexbox card grids end with one enormous card.
Use auto-fill to keep the track structure even when there are not enough items, and auto-fit to collapse the empty tracks so the few items you have stretch across the full width. That single word is the difference between three cards sitting at the left and three cards spread across the row.
Flexbox: the toolbar
.toolbar {
display: flex;
align-items: center;
gap: 12px;
}
.toolbar .spacer { margin-inline-start: auto; }
An automatic margin inside a flex container absorbs all the free space on that side, which pushes everything after it to the end. It is cleaner than justify-content: space-between as soon as you have more than two groups, because it does not depend on the item count.
Grid: overlapping content
.hero {
display: grid;
}
.hero > * {
grid-area: 1 / 1; /* every child in the same cell */
}
Stacking an image and its caption without absolute positioning. Because the children are still in flow, the grid sizes itself to the tallest of them — which absolute positioning cannot do.
Both: the classic app shell
.app {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100svh;
}
Grid for the shell, flexbox inside each region. This is the normal arrangement in real codebases and the reason “which one” is rarely an either-or question — a page typically nests both, several levels deep.
Subgrid, and the alignment problem it fixes
The long-standing limitation of grid was that only direct children participate in the tracks. A card with a heading and a body could not align its internals with the card next to it, because each card was its own formatting context. subgrid fixes exactly that:
.cards { display: grid; grid-template-rows: auto 1fr auto; }
.card { display: grid; grid-row: span 3; grid-template-rows: subgrid; }
Now every card’s heading, body and footer sit on the same three rows, so the footers line up no matter how long the headings are. Support is Firefox 71, Safari 16.0 and Chrome and Edge 117, at about 92% globally — it is safe to use now, and it removes the most common reason people used to fake alignment with fixed heights.
What about masonry?
The Pinterest-style waterfall is the one common layout neither mode does natively. The standardized answer is CSS Grid Lanes — and the name matters, because most articles still describe this feature as “CSS masonry” with grid-template-rows: masonry, a syntax that lost. The current shape is a display value:
.wall {
display: grid-lanes;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
The short version
- One axis, content sizes itself, wrapping is fine: flexbox.
- Two axes, or anything must align across rows: grid.
- Equal columns: grid with
1fr, or flexbox withflex: 1— but notflex-grow: 1. - Content-proportional columns: flexbox with
flex: auto. - Push one thing to the end: flexbox with an automatic margin.
- Overlap without leaving flow: grid, everything in
grid-area: 1 / 1. - Things overflowing:
min-width: 0, orminmax(0, 1fr).
Frequently asked questions
Should I use flexbox or grid?
Use grid when the layout’s shape is known ahead of the content and items must align across both rows and columns. Use flexbox when the content determines the sizes and you only need to distribute space along one axis. The deciding test is whether moving one item should affect an item in a different row — if yes, that is grid, because flexbox lines size independently of each other.
Is grid only for page layout and flexbox only for components?
No. That rule was a teaching simplification and it misroutes common cases in both directions: a card component whose internals must align with neighbouring cards is a grid problem, and a page-level toolbar that pushes a button to the end is a flexbox problem. Choose by whether you need one axis or two, not by the size of the region.
What does flex: 1 actually mean?
flex: 1 expands to flex: 1 1 0% — grow 1, shrink 1, and a flex-basis of zero. Because every item starts from zero, the available space is divided equally and the items end up the same width regardless of content. flex: auto is 1 1 auto, which starts each item at its content size and shares only the leftover space, giving content-proportional widths.
Why is flex-grow: 1 not making my columns equal?
Because flex-grow alone leaves flex-basis at its initial value of auto, so each item starts at its content width and only the surplus is shared equally. Items with more content stay wider. Write flex: 1 instead, which also sets the basis to zero and produces genuinely equal columns.
Why do my flex items overflow their container?
A flex item’s min-width resolves to auto, which means it will not shrink below the intrinsic width of its content. Long unbroken strings, wide tables and preformatted blocks therefore push past their share. Set min-width: 0 on the item — or min-height: 0 in a column — or apply overflow: hidden to it, which has the same effect on the automatic minimum size.
Why should I write minmax(0, 1fr) instead of 1fr?
Because 1fr is shorthand for minmax(auto, 1fr), and that auto minimum stops the track shrinking below its content — the grid equivalent of the flexbox overflow problem. Writing minmax(0, 1fr) sets the floor to zero, so the track honours the container width and its contents scroll or truncate instead of overflowing.
Can I use flexbox and grid together?
Yes, and most real layouts do. A grid element’s children can each be flex containers, and a flex item can be a grid container. The usual pattern is grid for the page shell and any region needing two-axis alignment, with flexbox inside individual rows and components.
What is the difference between auto-fill and auto-fit?
auto-fill creates as many tracks as fit, leaving empty ones in place when there are not enough items, so three cards in a six-track row stay at their minimum width on the left. auto-fit collapses the empty tracks to zero, so the items stretch to fill the whole row. With enough items to fill every track, the two behave identically.
What problem does subgrid solve?
It lets a nested grid use its parent’s track definitions instead of creating its own, so descendants can align to the outer grid’s lines. The standard case is a row of cards whose headings, bodies and footers must line up across cards regardless of text length — previously only achievable with fixed heights. Support is Firefox 71+, Safari 16+ and Chrome and Edge 117+, roughly 92% globally.
Can I do masonry layout in CSS yet?
Not in a stable browser release as of August 2026. The standardized feature is CSS Grid Lanes, used as display: grid-lanes — not the earlier grid-template-rows: masonry syntax that many articles still show. It is in Safari Technology Preview and behind a flag in Chrome and Edge 140+, so treat it as a progressive enhancement over a column-based fallback.
Read any layout without guessing
Display mode, grid tracks, flex values, gaps and the full box model for any element you hover — plus fonts, colors and design tokens, computed in your browser.
Add CSS DNA to Chrome — Free