Frontend
CSS Layout: Flexbox vs. Grid
Flexbox and Grid solve different problems, not the same problem twice — here's the mental model that makes the choice obvious.
Every so often a CSS layout goes wrong not because either Flexbox or Grid is broken, but because the wrong one was reached for. They overlap just enough — both can center things, both can space things out — that it's easy to treat them as interchangeable and pick whichever one you remember the syntax for. They're not interchangeable. They were built to solve different shapes of problem, and once that clicks, the "which one do I use" question mostly answers itself.
One axis versus two
Flexbox is fundamentally one-dimensional. You pick a single axis — a row or a column — and Flexbox's entire job is to distribute and align items along that axis: how much space each item takes, how the leftover space is shared out, how items line up on the cross axis. Wrapping onto a second line is supported, but it's a fallback behavior, not the model's primary concern — once a flex container wraps, each line is laid out independently, with no awareness of what's happening on the line above or below it.
Grid is fundamentally two-dimensional. You define rows and columns together, as a single structure, and then place items into specific cells or spans of that structure. A grid item on row 2 knows exactly how it relates to the item on row 1, because they both belong to the same coordinate system. That's the capability Flexbox simply doesn't have — true row-and-column awareness.
A mental model, not a property list
The fastest way to pick the right tool isn't memorizing which one has justify-content and which has grid-template-columns — it's asking one question: am I arranging things along one line, or do I actually have a row-and-column structure?
- A navbar with a logo on the left, links in the middle, and a button on the right — one row, items distributed along it. Flexbox.
- A button group, a tag list, a form's label-and-input pair, anything you'd describe as "centered" or "spaced out" — one axis. Flexbox.
- A dashboard with a sidebar, a header, and a main content area that all need to line up with each other — a real 2D structure. Grid.
- A gallery of cards that needs to stay aligned in both directions as the count changes — rows and columns that depend on each other. Grid.
They compose, they don't compete
The most common real-world pattern isn't "Grid or Flexbox" — it's both, nested. Use Grid to lay out the page shell (header, sidebar, main, footer), and use Flexbox inside each of those cells to align whatever content lives there. Reaching for one doesn't mean abandoning the other.
Seeing it side by side
The same navbar, written both ways, makes the difference concrete. The Flexbox version only knows about "items on a line" and lets justify-content: space-between push the outer two apart. The Grid version names three explicit tracks and drops each piece into its own named area — more code for something this simple, but it scales cleanly the moment the layout grows a second row.
/* Flexbox: one axis — distribute items along a row */
.navbar-flex {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Grid: two axes — rows AND columns, named and placed explicitly */
.navbar-grid {
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-areas: "logo links actions";
align-items: center;
}
.navbar-grid .logo { grid-area: logo; }
.navbar-grid .links { grid-area: links; }
.navbar-grid .actions { grid-area: actions; }The gotcha Flexbox always gets you on
Almost everyone has hit the moment where a flex item stubbornly refuses to shrink, no matter how much min-width: 0 or overflow: hidden gets thrown at it. The culprit is usually flex-shrink's quiet partner, flex-basis. The flex shorthand — flex: 1 — expands to flex-grow: 1, flex-shrink: 1, flex-basis: 0%, but plenty of components set an explicit width or rely on content size instead, which becomes the item's effective basis. Shrinking then has to fight against that basis, and against the item's intrinsic minimum size (a long unbreakable word, an image, a fixed-width child), which wins by default unless you override it.
The gotcha Grid always gets you on
Grid's equivalent surprise is the implicit grid. Define three explicit columns with grid-template-columns and place a fourth item without an explicit position, and Grid doesn't error — it just creates a new implicit row (or column) to hold it, sized by grid-auto-rows / grid-auto-columns, which default to auto. That's usually invisible until the implicit track ends up a different size than the explicit ones, and suddenly one row in an otherwise even grid is taller or shorter than the rest for no apparent reason. The fix is almost always to either place every item explicitly or set grid-auto-rows to match the explicit tracks on purpose.
Don't fight the model
If you find yourself adding a second and third nested flex container just to fake row-and-column alignment, that's usually the signal to switch to Grid instead of adding more wrappers. Conversely, if a Grid layout's only real job is centering one thing inside another, Flexbox (or plain place-items: center on a single-cell grid) will get there with less setup.
Key takeaways
- Flexbox is one-dimensional: it distributes and aligns items along a single row or column.
- Grid is two-dimensional: rows and columns are defined together, with items placed explicitly into that structure.
- Choose based on shape, not habit — one line of items means Flexbox, a real row-and-column structure means Grid.
- Flex items that won't shrink are almost always a flex-basis or min-content-size issue, not a flex-shrink bug.
- Undefined grid tracks don't error, they auto-generate via grid-auto-rows/columns — which can silently mis-size a track.
- Grid for the page shell, Flexbox for aligning content inside a cell, is a normal and effective combination.
