
CSS Grid vs. Flexbox: When to Use Which?
CSS Grid and Flexbox are both fully supported in every modern browser, both handle alignment beautifully, and both can produce layouts that look almost identical. So it's no surprise that "should I use Grid or Flexbox here?" is one of the most common questions in front-end development.
The short answer is that they aren't competitors. They were designed to solve different problems, and most real interfaces use both, often nested inside each other. The longer answer, which this article covers, is about understanding how each one thinks, so you can pick the right tool in seconds instead of fighting the wrong one for an hour.
The Core Difference: One Dimension vs. Two
The most repeated explanation is that Flexbox is one-dimensional and Grid is two-dimensional. That's true, but it's worth unpacking.
A flex container lays out items along a single axis, either a row or a column. When items wrap onto a new line, each line is its own independent flex line. Items in line two have no idea how wide the items in line one are. They don't line up in columns unless you force them to with fixed widths.
A grid container defines rows and columns at the same time. Every item is placed into cells on that shared track structure, so items automatically line up both horizontally and vertically.
Here's the difference in practice. Imagine five cards that wrap:
<div class="cards">
<article class="card">One</article>
<article class="card">Two</article>
<article class="card">Three</article>
<article class="card">Four</article>
<article class="card">Five</article>
</div>
With Flexbox:
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.card {
flex: 1 1 200px;
}
On a wide screen you might get three cards on the first row and two on the second. Because each flex line distributes its own space, the two cards on the last row grow to fill the entire width, making them wider than the cards above. Sometimes that's what you want. Often it isn't.
With Grid:
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
Now every card sits in a column track. The last row's two cards are exactly as wide as the ones above, leaving an empty slot at the end. The layout reads as a tidy grid.
Content-Out vs. Layout-In
A more useful mental model than "one vs. two dimensions" is the direction in which sizing decisions flow.
Flexbox is content-out. You hand the browser a bunch of items, and it figures out how big each one should be based on its content, then distributes leftover space according to flex-grow and flex-shrink. The items drive the layout.
Grid is layout-in. You define the structure first (columns, rows, areas), and items are placed into it. The container drives the layout.
This is why Flexbox feels natural for things like navigation bars, button groups, and tag lists, where you don't know or care exactly how wide each item is. And it's why Grid feels natural for page layouts, dashboards, and galleries, where you want a predictable structure no matter what content ends up inside.
When Flexbox Is the Better Choice
Navigation bars and toolbars
A row of links with a logo on the left and actions on the right is a classic flex layout:
<header class="site-header">
<a class="logo" href="/">TideWave</a>
<nav class="nav-links">
<a href="/blog">Blog</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<button class="cta">Subscribe</button>
</header>
.site-header {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1rem 2rem;
}
.nav-links {
display: flex;
gap: 1rem;
}
.cta {
margin-left: auto;
}
The margin-left: auto on the button absorbs all remaining free space and pushes it to the far right. Items size themselves from their content. There's no need to define tracks.
Components with a flexible middle
A media object, such as an avatar next to a comment, wants a fixed piece and a flexible piece:
.comment {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.comment-avatar {
flex: 0 0 48px;
}
.comment-body {
flex: 1;
min-width: 0; /* allow long words or URLs to wrap/shrink */
}
That min-width: 0 is important and often forgotten. Flex items default to min-width: auto, which prevents them from shrinking below their content's minimum size. A long URL in the comment body will otherwise blow out the layout.
Tag lists and chips that wrap naturally
When items vary in width and you want them to flow like words in a sentence, Flexbox with wrapping is ideal:
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
Each tag is exactly as wide as its text. Grid would force them into equal columns, which looks wrong for this kind of content.
Distributing space between a handful of items
Need three buttons to share a row equally, or one input to take all the space next to a submit button? Flexbox's flex property is built for this:
.search-form {
display: flex;
gap: 0.5rem;
}
.search-form input {
flex: 1;
}
When Grid Is the Better Choice
Page-level layouts
Header, sidebar, main content, footer. This is Grid's home turf, especially with named areas:
<div class="page">
<header class="page-header">Header</header>
<aside class="page-sidebar">Sidebar</aside>
<main class="page-main">Main</main>
<footer class="page-footer">Footer</footer>
</div>
.page {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100svh;
}
.page-header {
grid-area: header;
}
.page-sidebar {
grid-area: sidebar;
}
.page-main {
grid-area: main;
}
.page-footer {
grid-area: footer;
}
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
Rearranging the layout for mobile is a matter of redrawing the ASCII-art map. The HTML doesn't change. Be mindful, though, that visual order and DOM order can drift apart; keyboard and screen reader users follow the DOM.
Card grids and galleries
The repeat(auto-fill, minmax()) pattern gives you a responsive grid with zero media queries:
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
gap: 1.25rem;
}
The inner min(240px, 100%) prevents overflow on very narrow screens where the container is smaller than 240px.
auto-fill keeps empty tracks when there aren't enough items to fill a row, while auto-fit collapses them so the existing items stretch. Use auto-fill when you want consistent card widths and auto-fit when you want a few items to spread across the row.
Anything that needs alignment across rows
Forms are an underrated Grid use case. Labels on the left, inputs on the right, all perfectly aligned:
.settings-form {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0.75rem 1.5rem;
align-items: center;
}
Each label and input pair naturally falls into the two columns, and the label column is exactly as wide as the longest label. Flexbox can't do this without fixed widths, because each row would be its own independent flex line.
Overlapping elements
Grid lets multiple items occupy the same cell, which makes overlays trivial without absolute positioning:
.hero {
display: grid;
}
.hero > * {
grid-area: 1 / 1;
}
.hero-text {
align-self: end;
padding: 2rem;
z-index: 1;
}
The image and the text share the same cell. The container still sizes itself to the tallest item, which is something position: absolute can't give you.
Subgrid: Aligning Nested Content
One classic Grid limitation used to be that nested elements couldn't align to the parent grid's tracks. Subgrid fixes this, and it's now supported in all major browser engines.
Consider cards where the title, body, and footer should line up across a row, even when titles have different lengths:
<div class="product-list">
<article class="product">
<h3>Compact Desk Lamp</h3>
<p>A small lamp for small desks.</p>
<a href="#">View</a>
</article>
<article class="product">
<h3>Adjustable Architect Lamp With Clamp Mount</h3>
<p>Long reach, strong clamp.</p>
<a href="#">View</a>
</article>
</div>
.product-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1rem;
}
.product {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
gap: 0.5rem;
}
Each card spans three rows of the parent grid and adopts those rows via subgrid. The titles, descriptions, and links now align across every card in the row. If you need to support older browsers, the fallback is simply that cards size independently, which is still perfectly usable.
Performance: Does It Matter?
Occasionally you'll read that one layout mode is "faster" than the other. For practically all real-world interfaces, the difference is negligible. Both are implemented natively and optimized heavily. Choose based on the layout you need, not micro-benchmarks. If you have a list of thousands of items, virtualization will matter far more than whether the container is flex or grid.
Using Them Together
The best layouts usually combine both. A typical pattern:
- Grid for the page skeleton (header, sidebar, main).
- Grid for the card collection inside main.
- Flexbox inside each card for the footer row of buttons and metadata.
- Flexbox for the header's navigation.
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
}
There's no rule that says you must commit to one. Every grid item can be a flex container, and every flex item can be a grid container.
Common Mistakes
Using Flexbox with percentage widths to fake a grid. If you find yourself writing flex: 0 0 33.333% and calculating gutters with calc(), you want Grid.
Using Grid for a simple row of items. Defining grid-template-columns: auto auto auto auto for a nav bar works until someone adds a fifth link. Flexbox handles variable counts gracefully. (You can use grid-auto-flow: column in Grid, but Flexbox is more natural here.)
Forgetting min-width: 0. Both flex items and grid items default to a minimum size based on content. A long word, a wide table, or a pre block can overflow its track. Add min-width: 0 to flex items, or use minmax(0, 1fr) instead of 1fr in grid columns.
.layout {
display: grid;
grid-template-columns: 240px minmax(0, 1fr);
}
Reordering visually without considering accessibility. Both order in Flexbox and placement in Grid can move items away from their source order. Tab order and screen readers still follow the DOM, so large visual reordering can confuse users.
A Quick Decision Checklist
Ask yourself these questions:
- Do items need to line up in both rows and columns? Use Grid.
- Is the layout defined by the container (fixed tracks, named areas)? Use Grid.
- Is it a single row or column where items size themselves by content? Use Flexbox.
- Do you want items to wrap like text, each with its own width? Use Flexbox.
- Do you need items to overlap without absolute positioning? Use Grid.
- Do you just need to center something? Either works.
display: grid; place-items: centeris the shortest.
Conclusion
Grid and Flexbox are partners, not rivals. Flexbox excels when content should dictate size along a single line: navigation, toolbars, tags, and small component internals. Grid excels when you want a structure that content fits into: page layouts, card collections, forms, and anything that must align across rows and columns.
Once you start thinking in terms of "content-out" versus "layout-in", the choice becomes obvious most of the time. And when it isn't, remember that nesting one inside the other is not a compromise; it's how modern CSS layouts are meant to be built.


