Comparing flexbox and grid for real layout work
· 7 min read
Flexbox vs grid with side-by-side code, a decision table, and the one minmax() line that needs zero media queries. Copy-ready CSS examples for both systems.

- Flexbox is one-dimensional; grid is two-dimensional
- Content-first sizing versus container-first planning
- Start with grid for the page skeleton, then flexbox inside components
- Both systems in one stylesheet is the normal end state
The core difference between flexbox and grid
The flexbox vs grid question resolves once you hold one idea: flexbox lays out items along a single axis, either a row or a column, while grid lays items out on both axes at once.
That single-dimensional versus two-dimensional distinction drives everything else, including which problems each solves naturally. As MDN's guide to their relationship puts the contrast, flexbox works from the content outward while grid works from the layout inward.
Concretely: a flex container asks "how wide are my children?" and distributes leftover space around them. A grid container asks "how many columns and rows do I have?" and drops children into the cells. Neither approach is better; they answer different questions, which is why the choice depends on what you know about the layout before you start.
The same layout in both systems
A three-column card row makes the difference visible. In grid, you describe the tracks:
.cards {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
}
In flexbox, you line items up and let them share space:
.cards {
display: flex;
gap: 1rem;
}
.cards > * {
flex: 1;
}
Both use gap for spacing, now standard across both layout systems, so no margin hacks are needed. Both produce three equal columns today.
They diverge tomorrow: when a fourth card arrives, the flexbox row squeezes four items into the same width or wraps (with flex-wrap), while the grid keeps three columns and flows the fourth into a second row automatically. If the design intent is "always exactly three columns," grid states that intent directly. If the intent is "these cards share space, however many there are," flexbox expresses it better.
A decision table instead of a verdict
| Layout problem | Reach for | Why |
|---|---|---|
| Page skeleton: header, sidebar, main, footer | Grid | Two-dimensional structure with named areas |
| Navbar items spread left and right | Flexbox | One-axis distribution is its core skill |
| Card gallery with strict column count | Grid | Track definitions enforce the count |
| Button group or form row | Flexbox | Content-sized items, easy spacing |
| Dashboard with spanning widgets | Grid | Line-based and area-based placement |
| Centering anything | Either | Both center cleanly on their axes |
When both answers look plausible, ask what happens when content changes. The system whose behavior under surprise matches the design intent is the right one.
When flexbox wins
Flexbox excels wherever a single line of content needs distribution (MDN's flexbox overview covers the axis model): navigation bars, button rows, tag lists, form field rows, and media objects with an image beside text. Its content-first sizing means items keep their natural width until told otherwise, which suits interfaces where labels and controls vary.
Wrapping with flex-wrap produces tag-cloud behavior that grid cannot match query-free, since grid's auto-fit trick only yields equal-width tracks, not variable-width ones. Per the MDN flexbox guide, alignment properties like justify-content and align-items (MDN's box alignment guide documents the full set) handle the spacing decisions that used to require margin hacks.
Inside a card component, flexbox aligns the title, body, and footer along one axis while letting content length vary. That is the component-internal case where it beats grid on simplicity every time.
When grid wins
Grid owns two-dimensional structure. Page skeletons with headers, sidebars, and footers map directly onto grid template areas, where named regions make the layout readable in the CSS itself. The fr unit defined in the CSS Grid specification distributes free space fractionally, so 1fr 2fr gives one column twice the share without pixel math.
Galleries that must show exactly four columns at every width, dashboards where widgets span multiple cells, and magazine-style arrangements all express naturally in grid and awkwardly in anything else.
Placement is grid's quiet superpower, built on line-based placement: items can sit at explicit line numbers or named areas regardless of source order, which solves visual-order versus DOM-order mismatches that flexbox handles poorly.
Using both together
The mature answer to flexbox versus grid is usually both. Grid defines the page skeleton: a header band, a content area split into main and sidebar, a footer. Inside each region, flexbox arranges the components: a navbar's items, a card's internals, a form row. This composition mirrors how designers think, structure first then details, and it keeps each stylesheet small because each system handles what it is good at.
These generators exist on our site precisely because this decision comes up on every project: the CSS grid layout generator, then tune component internals with the flexbox generator; both output copy-ready CSS. When the stylesheet ships, the CSS minifier strips the whitespace before deployment.
CSS grid vs flexbox for responsive layouts
The responsive question has a cleaner answer than most expect, and it splits by column behavior. When columns should stay equal and the count should adapt, grid's repeat(auto-fit, minmax(240px, 1fr)) delivers responsive columns with no media queries at all.
One subtlety matters here: auto-fit collapses empty tracks entirely, so a three-item gallery using it stretches those three items across the full width, while auto-fill keeps the empty tracks and preserves the column rhythm. Choosing between them is a one-word decision with visible consequences.
When items have genuinely variable widths, tags, chips, filter buttons, flexbox with flex-wrap remains the right tool, because every item keeps its natural size and rows form wherever they break. Equal-width grid tracks cannot fake that behavior. The honest summary: query-free responsiveness for uniform columns comes from grid, and query-free responsiveness for ragged content comes from flexbox.
Named areas: grid at its most readable
Grid template areas deserve their own look because they make layouts self-documenting:
.page {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 240px 1fr;
}
Each quoted string is one grid row; each word is a named cell. Children then claim their region with grid-area: header and the layout reads like the diagram it represents. Responsive variants simply redefine the areas at a breakpoint, collapsing "sidebar main" into a single column, and the whole page restructures through one property change. Flexbox has no equivalent because one axis cannot describe a map.
Common mistakes worth avoiding
| Mistake | Symptom | Fix |
|---|---|---|
| Building full page layouts in flexbox | Nested wrappers fighting each axis | Grid for the skeleton |
| Using grid for a single toolbar | Track boilerplate for one row | Flexbox for one-axis distribution |
| Forgetting minmax() in grid tracks | Columns overflow on small screens | repeat(auto-fit, minmax(240px, 1fr)) |
| Expecting wrapped flex rows to align | Rows behave independently | Grid when cross-row alignment matters |
The minmax pattern deserves special mention: repeat(auto-fit, minmax(240px, 1fr)) produces responsive columns with zero media queries, and it is the single highest-value grid line most developers learn.
Choosing between flexbox and grid from here
Resolve every future flexbox vs grid question with two checks: how many dimensions does the layout have, and do you know the structure before seeing the content. Two dimensions or known structure means grid; one axis or content-driven sizing means flexbox; most real pages mean both working together. For the wider toolkit around this decision, see the complete guide to developer tools, and when the next layout debate starts, sketch the skeleton in the grid generator first; the right system usually announces itself within one draft.
Related Tools

Written by
Beatrice Mwangi
A formatter settles arguments that should never have been opened, which leaves one decision worth having: which tool the project commits to. Everything after that is configuration. The current field has converged on two implementations with the same goal. Prettier has the larger plugin ecosystem and a stable output model; Biome is written in Rust, is substantially faster on large files, and covers formatting and linting in one pass with its own configuration.
Both produce readable output and both will change a file the moment somebody saves it. Adopting one is a project decision, not a preference. Configuration discovery is where people get stuck. Both tools read a configuration file, resolve it from the directory being formatted, and ignore paths listed in an ignore file.
Running a formatter from the wrong directory changes which configuration applies, so the output changes without anybody editing the rules. Format and lint are different jobs with an ordering. A linter reports problems; a formatter rewrites files. Running lint with a fix option and formatting at the same time produces two passes over the same file and a diff nobody can read.
I fix the order first, then configure. I have a view on print width and no interest in arguing it. A formatter that disagrees with hand-written code produces a first commit of pure reformatting, and that commit has to be separate from any functional change or the review is unreadable. The practical recommendation on every page is the same: adopt one tool, format once on a clean branch, and then let it run on save forever.