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

CSS Grid vs Flexbox — A Decision Guide for Real Layouts

Stop guessing which one to use. Here's a practical framework for choosing between Grid and Flexbox based on actual layout problems.

Ștefan Binisor

Ștefan Binisor

Co-founder, peal.dev

CSS Grid vs Flexbox — A Decision Guide for Real Layouts

We've seen this pattern in every codebase we've touched: Flexbox everywhere, including places where it's clearly fighting the layout. Nested flex containers three levels deep, `flex-wrap` hacks to simulate a grid, margin auto tricks that break the moment you add one more item. It works, technically. But it shouldn't.

The confusion is understandable. Both Grid and Flexbox can achieve similar results in many cases. But they're designed for fundamentally different problems. Once that clicks, the right choice becomes obvious 90% of the time.

The One-Sentence Mental Model

Flexbox is for laying out items in one direction — a row or a column. Grid is for laying out items in two directions at once — rows AND columns. That's it. Everything else flows from this.

If your layout decision is primarily about how items flow along a single axis — a navigation bar, a row of cards that wrap, a vertical stack of form fields — Flexbox is your tool. If you're thinking in rows AND columns simultaneously — a dashboard grid, a page layout with sidebar and header and main content — that's Grid territory.

Flexbox: you control one axis and let the other adapt. Grid: you control both axes explicitly.

When Flexbox Is the Right Call

Flexbox shines when the content drives the layout. You have a row of buttons and you want them spaced out. You have a nav with a logo on the left and links on the right. You have a card with an icon and some text and you want them vertically centered. These are Flexbox jobs.

/* Navigation bar — classic Flexbox use case */
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.5rem;
  height: 64px;
}

/* Card with icon + text aligned */
.card-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* Button group that wraps gracefully */
.button-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

Notice what these have in common: one axis is doing the real work. The nav is a row. The card header is a row. The button group is a row that wraps. Flexbox handles all of them cleanly.

The `gap` property is criminally underused in older Flexbox code. People used to do `margin-right` on all children except the last one, or negative margins on the container. Just use `gap`. It's been supported everywhere since 2021.

  • Navigation bars and toolbars
  • Centering something both horizontally and vertically (display: flex + align-items/justify-content on the parent)
  • Distributing items along a row with space-between
  • Vertically stacking form fields or list items
  • Any component where items should be sized by their content
  • Wrapping pill/tag components

When Grid Is the Right Call

Grid shines when the layout drives the content. You have a design in your head — or in Figma — and you need elements to land in specific positions regardless of their content size. You want a 12-column page layout. You want a photo gallery where images all stay the same size. You want a dashboard where panels snap into a defined structure.

/* Page layout with sidebar */
.app-layout {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 64px 1fr;
  grid-template-areas:
    "sidebar header"
    "sidebar main";
  min-height: 100vh;
}

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

/* Auto-fit card grid — no media queries needed */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

That `auto-fit` + `minmax` pattern is one of the most powerful things in CSS. Cards that automatically wrap into fewer columns on smaller screens, with no JavaScript and no media queries. We use this in basically every template we build.

Grid also wins whenever you need items in different rows to align with each other. Flexbox can't do this — each row is independent. Grid treats the whole container as a unified coordinate system. If you've ever tried to build a form where labels in one column need to align with inputs in another, you know exactly why this matters.

  • Full page layouts (header, sidebar, main, footer)
  • Dashboard panels that need to align across rows
  • Image/card galleries with consistent sizing
  • Any layout you'd describe with rows AND columns
  • CSS art or precise positioning challenges
  • Overlapping elements (place two children in the same grid cell)

The Cases That Trick You

Some layouts look like they could go either way. Here's how we think through the tricky ones.

**Wrapping card lists**: If you have 9 cards and you want them in rows of 3, your instinct might be Flexbox with `flex-wrap`. This works but you have to manage the gap and the last row behaves weirdly if it doesn't fill (you get orphaned cards that stretch or align weird). Grid with `repeat(3, 1fr)` or `auto-fit` is cleaner and gives you consistent alignment across rows.

**Centered hero sections**: A single element you want centered on screen — most people reach for Flexbox (`display: flex; align-items: center; justify-content: center`). Grid also works perfectly here (`display: grid; place-items: center`). Either is fine. We usually use Grid for page-level centering and Flexbox for component-level centering, just to be consistent.

**Responsive sidebars**: You want a sidebar + main content layout that stacks vertically on mobile. Grid with `grid-template-columns` and a media query to change it to `1fr` is much more readable than a Flexbox solution with `flex-direction: column` override.

/* Sidebar layout — Grid makes this obvious */
.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
}

@media (min-width: 768px) {
  .layout {
    grid-template-columns: 260px 1fr;
  }
}

/* Versus the Flexbox version — more work */
.layout {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

@media (min-width: 768px) {
  .layout {
    flex-direction: row;
  }

  .sidebar {
    width: 260px;
    flex-shrink: 0;
  }

  .main {
    flex: 1;
    min-width: 0; /* don't forget this or content overflows */
  }
}

That `min-width: 0` in the Flexbox version — we've had to debug that exact overflow bug at 2am on a client project. Flex children have `min-width: auto` by default, which means a child with text content or a wide image can blow out the layout. Grid children don't have this problem. Another quiet reason to prefer Grid for page-level structure.

Using Both Together (This Is Normal)

The best layouts use Grid at the macro level and Flexbox at the micro level. Grid handles the page skeleton — where the sidebar goes, where the header goes, how many columns the content area has. Flexbox handles the internals of each component — the navigation links, the card footer with a button pushed to the right, the icon-text pairs.

// Real example: dashboard layout in Next.js + Tailwind
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    // Grid handles the macro layout
    <div className="grid grid-cols-[240px_1fr] grid-rows-[64px_1fr] min-h-screen">
      <aside className="row-span-2 border-r bg-gray-50">
        {/* Flexbox handles nav internals */}
        <nav className="flex flex-col gap-1 p-4">
          <NavItem href="/dashboard" icon={<HomeIcon />} label="Home" />
          <NavItem href="/dashboard/settings" icon={<SettingsIcon />} label="Settings" />
        </nav>
      </aside>

      {/* Flexbox handles header internals */}
      <header className="flex items-center justify-between px-6 border-b">
        <h1 className="font-semibold">Dashboard</h1>
        <UserMenu />
      </header>

      <main className="p-6 overflow-auto">
        {/* Grid again for card layout */}
        <div className="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-4">
          {children}
        </div>
      </main>
    </div>
  );
}

This is the pattern we use in most of our templates at peal.dev. Grid for structure, Flexbox for components. Once you internalize this, your layout code becomes much more intentional and much easier to maintain.

Quick Decision Checklist

When you're staring at a new layout and can't decide, run through these questions:

  • Are items flowing in one direction only (row or column)? → Flexbox
  • Do you need items to align across both rows AND columns? → Grid
  • Is content size driving the layout? → Flexbox
  • Is a predefined structure driving the layout? → Grid
  • Do you need items in different rows to align with each other? → Grid (Flexbox literally cannot do this)
  • Are you centering one thing inside another? → Either works, pick your preference
  • Is this a component's internals (button, card, nav item)? → Flexbox
  • Is this a page section or major structural region? → Grid
If you're writing more than two lines of Flexbox just to simulate a grid, stop. You want Grid.

The One Thing to Stop Doing

Stop using Flexbox for page-level layouts out of habit. We see this constantly — a main layout wrapper with `display: flex; flex-wrap: wrap` and then a bunch of percentage widths on children to fake columns. It works until someone adds a new section, or until you need things to align across the fake rows, and then it falls apart.

Grid support is excellent everywhere now. There's no IE11 to worry about. `grid-template-areas` is readable enough that even designers can understand it. `auto-fit` and `minmax` handle responsiveness better than most JavaScript-based solutions. There's genuinely no reason to avoid it.

The practical takeaway: next time you start a layout, ask yourself whether you're thinking about one axis or two. One axis — type Flexbox. Two axes — type Grid. The rest of the properties will follow naturally from that decision, and you'll stop fighting your CSS.

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