50% off SaaS Starter Kit — only for the first 100 buildersGrab it →
← Back to blog
best-practicesMay 23, 2026·8 min read

CSS Grid vs Flexbox: A Decision Guide for Real Layouts

Stop guessing which one to use. Here's how we actually decide between Grid and Flexbox when building real UI — with no hand-wavy theory.

Robert Seghedi

Robert Seghedi

Co-founder, peal.dev

CSS Grid vs Flexbox: A Decision Guide for Real Layouts

Every few months someone asks 'should I use Grid or Flexbox here?' and every few months the internet answers with 'it depends' and a diagram that somehow makes things worse. We've built enough UIs at this point to have an actual opinion, so here it is: Grid and Flexbox solve different problems, and once you internalize *which* problems, you stop second-guessing yourself.

The short version: Flexbox is for laying things out in one direction. Grid is for two dimensions. But that's like saying a knife is for cutting — technically true, completely useless. Let's go deeper.

The Mental Model That Actually Sticks

Here's how we think about it: Flexbox is *content-driven*. The container says 'here's some space, items, figure it out.' Grid is *layout-driven*. You define the structure first, then items slot in. This distinction matters more than the one-dimension vs two-dimension thing.

With Flexbox, the content influences the layout. Items can grow, shrink, wrap — the container reacts to what's inside it. With Grid, you're drawing lines on a page first, then placing things. The layout controls the content, not the other way around.

Flexbox: 'I have these items, where do they go?' Grid: 'I have this space, how is it divided?'

This is why a navbar is almost always Flexbox — you have a logo, some nav links, and maybe a button, and you want them spaced out horizontally. The content defines the layout. But a dashboard with a sidebar, header, and main content area? That's Grid — you're defining zones and filling them.

When to Reach for Flexbox

Flexbox is your default tool for component-level layout. Anytime you're arranging items along a single axis — horizontally or vertically — and you want flexible sizing, Flexbox is almost certainly right.

  • Navigation bars and header layouts
  • Button groups and icon + text combinations
  • Card footers where you want one item pushed to the right
  • Centering a single element (vertically and horizontally)
  • Input fields with attached labels or icons
  • Stacking items vertically in a sidebar or panel
  • Any list of items that should wrap onto new lines responsively
/* Classic: push last item to the end */
.card-footer {
  display: flex;
  align-items: center;
  gap: 8px;
}

.card-footer .author {
  flex: 1; /* takes up remaining space */
}

.card-footer .timestamp {
  /* naturally pushed to the right */
  color: var(--muted);
  font-size: 0.875rem;
}

/* Center anything, the reliable way */
.hero-section {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

The `flex: 1` trick is something we use constantly. It says 'take up all remaining space' and it's the cleanest way to push elements to opposite ends of a container without reaching for `margin-left: auto` (though that works too, and isn't a sin).

When to Reach for Grid

Grid shines whenever you need explicit control over two axes simultaneously, or when you want items to align across rows *and* columns. The moment you find yourself fighting Flexbox to make things line up between rows, switch to Grid.

  • Page-level layout (sidebar + main + header + footer)
  • Card grids where cards should align in rows and columns
  • Any layout where items in one row should align with items in another
  • Magazine-style or asymmetric layouts
  • CSS-only responsive grids without media queries (using auto-fill/auto-fit)
  • Overlapping elements (Grid lets you stack things in the same cell)
/* Page layout with named areas */
.app-shell {
  display: grid;
  grid-template-areas:
    'header header'
    'sidebar main'
    'sidebar footer';
  grid-template-columns: 240px 1fr;
  grid-template-rows: 64px 1fr auto;
  min-height: 100vh;
}

.app-header  { grid-area: header; }
.app-sidebar { grid-area: sidebar; }
.app-main    { grid-area: main; }
.app-footer  { grid-area: footer; }

/* Responsive card grid, no media queries needed */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

That `repeat(auto-fill, minmax(280px, 1fr))` line is magic. Cards will be at least 280px wide, fill available space, and wrap automatically. No media queries. No JavaScript. We use it in almost every template we've built.

The Cases Where People Get Confused

Let's go through the situations where the choice isn't obvious.

**A row of cards that should all be the same height.** Flexbox can do this — `align-items: stretch` is the default — but if you want the *content inside* those cards to align across cards (like the footer of each card sitting at the bottom regardless of content length), you need either Grid on the outer container *and* Flexbox inside each card, or `subgrid`.

/* Cards same height, footer always at bottom */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

.card {
  display: flex;
  flex-direction: column; /* inner layout is Flexbox */
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 20px;
}

.card-body {
  flex: 1; /* grows to fill space */
}

.card-footer {
  margin-top: 16px;
  /* always at the bottom, regardless of body content length */
}

This is the combo pattern: Grid for the outer structure, Flexbox for the inner component. It's not either/or. We use them together constantly.

**Centering a modal or dialog.** Either works, but Grid is actually cleaner for this specific case because you can center *and* control the overlay in one declaration:

/* Grid centering for modals */
.modal-overlay {
  display: grid;
  place-items: center; /* shorthand for align-items + justify-items */
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.5);
}

/* Flexbox centering (equally valid) */
.modal-overlay-flex {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  inset: 0;
}

`place-items: center` is one of those things that feels like cheating. It's a shorthand for `align-items` and `justify-items` in one go, and it works on both Grid and Flexbox (as `place-content` for Flex). Stefan discovered this at some ungodly hour and we've used it ever since.

The Decision Tree We Actually Use

Here's the actual thought process when we sit down to build a layout:

  • Is this a page-level layout or a component? Page-level → Grid first.
  • Am I arranging items in one direction (row OR column)? → Flexbox.
  • Do I need items in different rows to align with each other? → Grid.
  • Is the layout structure more important than the content size? → Grid.
  • Am I fighting with Flexbox to get something to line up? → Switch to Grid.
  • Do I need something to overlap or stack in the same space? → Grid (use named lines or `z-index` within a grid cell).
  • Is it a list of things that should wrap? → Could be either; try Flexbox with `flex-wrap: wrap` first, Grid if alignment matters.

The 'am I fighting with it' signal is real. Both tools are expressive enough that you can technically make either work for most layouts, but when you find yourself adding `margin: -2px` or a wrapper div just to fix alignment, you're using the wrong tool.

What About Tailwind?

All of this applies directly to Tailwind classes — `flex`, `grid`, `grid-cols-3`, `gap-4`, etc. The mental model is identical, you're just writing utility classes instead of CSS properties. The Tailwind docs are actually solid for this if you want the class-by-class reference.

One thing worth knowing: Tailwind's `grid-cols-{n}` sets equal columns, but for the `minmax` auto-fill pattern you'll want to drop into arbitrary values: `grid-cols-[repeat(auto-fill,minmax(280px,1fr))]`. Not the prettiest, but it works, and we use it in our templates at peal.dev to avoid defining custom CSS for responsive grids.

You'll use both in every real project. The goal isn't to pick a winner — it's to know which one is right for each layer of your UI.

A Few Things That Trip People Up

**Flexbox gap.** For years you had to use margins for spacing between flex items. Now `gap` works on Flexbox too (it's been supported in all major browsers since 2021). Stop using `margin-right` on every child except the last one. `gap: 16px` and move on.

**`fr` units in Grid.** The `fr` (fraction) unit is the thing that makes Grid genuinely powerful for responsive layouts. `grid-template-columns: 1fr 2fr` gives you a 1:2 ratio that adapts to any container width. `1fr 1fr 1fr` is cleaner than `33.33% 33.33% 33.33%` and doesn't have the rounding issues.

**`align-content` vs `align-items`.** This one bites everyone at least once. `align-items` aligns items within their grid cell or flex line. `align-content` distributes the rows/lines themselves within the container. If you're trying to space out rows and nothing's working, you probably want `align-content`.

**Implicit vs explicit Grid.** When you define `grid-template-columns` but not `grid-template-rows`, Grid will create rows automatically as needed. These are implicit rows. By default they're sized to fit content, but you can control them with `grid-auto-rows`. We usually set `grid-auto-rows: min-content` or a fixed value to avoid surprises.

/* Explicit columns, auto rows with minimum height */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(120px, auto); /* at least 120px, grows with content */
  gap: 16px;
}

The Bottom Line

Flexbox for components. Grid for layouts. Both for complex UIs. That's 80% of the answer. The other 20% is pattern recognition — you build enough UIs and you stop thinking about it.

The worst thing you can do is commit to one tool for everything. We've seen codebases where someone decided to do all layout with Grid and ended up with `grid-template-columns: 1fr` everywhere just to use flex-like behavior. We've also seen Flexbox used for full page layouts with deeply nested wrappers to fake two-dimensional control. Both are painful to maintain.

If you want to see these patterns in production code, our templates at peal.dev use both throughout — page shells in Grid, components in Flexbox, and the combo pattern for card layouts. Reading real code is worth a hundred blog posts.

Pick the right tool, write less CSS, sleep more. That's the whole guide.

Newsletter

Liked this post? There's more where it came from.

Dev guides, honest build stories, and the occasional 2am debugging confession — straight to your inbox. No spam, unsubscribe anytime.

Browse templates
Written by humansWeekly dropsSubscriber perks

Join the Discord

Ask questions, share builds, get help from founders