A few days ago, I reviewed a pull request with something like this:
.card {
flex: 1 1 calc(33.333% - 1.5rem);
margin: 0.75rem;
}
It "worked." Until someone added a 4th card, and it stretched all the way across the second row like a giant billboard.
To fix that, the developer added an invisible dummy element at the end to balance the flex row. Then another media query to reset widths on tablet. Suddenly, a basic 3-column card grid was 45 lines of CSS and two weird DOM hacks.
This happens all the time because most of us learned Flexbox first, got comfortable with it, and started treating it like a hammer for every layout nail.
CSS Grid has been supported in 100% of modern browsers for over half a decade. You don't need to fight flex-wrap and calc() anymore.
Here is how I actually decide between them in real production work.
The Rule of Thumb: Content-Out vs. Layout-In
You don't need a 20-page specification to decide. You only need to answer one question:
Who decides the sizing? The content, or the container?
Flexbox is "Content-Out" (1D)
In Flexbox, the items inside dictate how things look. A button with a long label gets wider. An icon sits snugly next to some text. The items push and flex along one axis at a time (either a row or a column).
Use Flexbox when you want items to flow naturally and distribute leftover space based on what’s inside them.
CSS Grid is "Layout-In" (2D)
In Grid, the parent container holds all the power. You carve out rows and columns first, and whatever you drop inside has to conform to those slots.
Use Grid when you want strict alignment across both rows AND columns, regardless of what content is inside the cards.
Example 1: Where Flexbox is King (Navbars)
Navbars are classic 1D layouts. You have a logo on the left, some links in the middle, and maybe an avatar or CTA button on the right.
<header class="navbar">
<div class="logo">Acme</div>
<nav class="nav-links">
<a href="/docs">Docs</a>
<a href="/pricing">Pricing</a>
<a href="/blog">Blog</a>
</nav>
<button class="btn-primary">Sign in</button>
</header>
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1.5rem;
}
.nav-links {
display: flex;
gap: 1rem;
}
Why Flexbox here?
- "Docs" is shorter than "Pricing". We don't care about equal widths; we just want natural spacing between words.
- If the brand name changes, the layout doesn't break. The items flex according to their own size.
Trying to build this header with CSS Grid would force you into rigid columns you don't actually want.
Example 2: Where Grid Destroys Flexbox (Cards & Galleries)
Now look at a product grid or photo gallery:
<ul class="card-grid">
<li>Card 1</li>
<li>Card 2</li>
<li>Card 3</li>
<li>Card 4</li>
</ul>
With Flexbox, you end up doing math: calc(25% - gap) and babysitting the last row so orphans don't stretch.
With Grid, it’s one declaration:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1.25rem;
}
Look at what this does:
- Zero media queries: On a phone (360px), it fits 1 column. On an iPad, 2 or 3 columns. On an ultrawide monitor, 5 columns. It adapts automatically.
- No orphan stretching: Card 4 stays in column 1 of row 2. It doesn't blow up to fill the whole screen.
- Equal height rows for free: If Card 2 has 4 lines of text and Card 1 has 1 line, both cards in that row automatically match height.
The "Last Item" Test
If you're ever stuck choosing, test what happens when your data count changes:
- If you have 5 items in a 3-column layout:
- Do you want items 4 & 5 to expand and split the bottom row evenly? Use Flexbox.
- Do you want items 4 & 5 to stay the exact same width as items 1–3 and leave the 3rd spot empty? Use Grid.
90% of the time, for dashboards, galleries, and product cards, you want the second behavior.
The Real Best Practice: Nest Them
You don't pick a team. You use both in the same component:
- Grid for the skeleton: The page layout, the sidebar/main content split, the product card grid.
- Flexbox for the organs: Inside the card (aligning an author avatar next to a timestamp), inside a button (centering an icon next to text).
/* Outer skeleton: CSS Grid */
.dashboard-layout {
display: grid;
grid-template-columns: 260px 1fr;
min-height: 100vh;
}
/* Inner component detail: Flexbox */
.user-badge {
display: flex;
align-items: center;
gap: 0.5rem;
}
Wrapping Up
Next time you catch yourself typing calc() inside a flex-basis or adding empty <div> tags just to make items line up vertically, hit backspace.
Define a grid on the parent, give it a gap, and let the browser do the heavy lifting.
What's your biggest layout pet peeve in CSS? Let me know in the comments!
Top comments (0)