Web Development

CSS Grid vs Flexbox: A Practical Decision Guide

Stop guessing which layout tool to reach for. A clear framework for choosing between Grid and Flexbox, with copy-paste patterns for the layouts you build every week.

Marcus Webb

Senior Writer

Hugo Nina

Fact Checked

Two robots building a webpage: one placing blocks in a rigid grid, the other arranging flexible floating shelves

Every developer who touches CSS eventually asks it: Grid or Flexbox? And the internet’s classic answer — “Grid is 2D, Flexbox is 1D” — is true but weirdly useless in the moment, like being told a hammer is for hammering. You’re staring at a card component, a nav bar, a pricing table. Which one do you write?

After building layout systems for more sites than I can remember, here’s the decision framework I actually use, followed by the concrete patterns that cover 95% of real-world layout work.

The one question that decides it

Ask yourself: who should be in charge — the content or the layout?

  • If the content should push the layout around — items should take the space they need, wrap when they run out of room, and nobody needs to line up with anything in another row — that’s Flexbox. It’s a content-out tool.
  • If the layout should discipline the content — things must land in defined areas, columns must align across rows, the design has a visible skeleton — that’s Grid. It’s a layout-in tool.

A navigation bar is content-out: links take their natural width, extra space goes wherever you send it. A dashboard is layout-in: the sidebar is 260px, the main area gets the rest, widgets snap to a grid. A photo gallery where every cell must be the same size regardless of the photo? Layout-in. A row of tags that wraps? Content-out.

This is the same distinction as “1D vs 2D,” but framed by intent, which is what you actually know when you start typing.

The intent question, visualized: content in charge → Flexbox; layout in charge → Grid.

Flexbox: the distribution engine

Flexbox excels at one job: distributing items and space along a line, with graceful wrapping. The patterns you’ll use constantly:

The nav bar (space-between with groups)

.navbar {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.navbar .actions {
  margin-left: auto; /* pushes actions to the far end */
  display: flex;
  gap: 0.5rem;
}

margin-left: auto inside a flex container is still one of the most useful lines in CSS — it eats all available space, shoving everything after it to the end.

The media object (avatar + text)

.media {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.media > img { flex-shrink: 0; }
.media > .body { min-width: 0; } /* lets long text truncate instead of overflowing */

That min-width: 0 fixes the single most common Flexbox bug: flex items refuse to shrink below their content size by default, so long unbroken strings (URLs, code) blow the layout open.

Vertical centering (finally trivial)

.center {
  display: flex;
  align-items: center;
  justify-content: center;
}

Tag lists and button rows that wrap

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

Note gap — it works in Flexbox in every modern browser and eliminates the ancient negative-margin hacks entirely.

Grid: the skeleton builder

Grid earns its keep the moment two dimensions need to agree with each other.

The page shell

.layout {
  display: grid;
  grid-template-columns: 260px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "sidebar header"
    "sidebar main"
    "sidebar footer";
  min-height: 100dvh;
}
.sidebar { grid-area: sidebar; }
.header  { grid-area: header; }

grid-template-areas is self-documenting — the CSS literally draws the layout. Rearranging the page for mobile is one media query that redraws the map.

The responsive card grid (no media queries)

The most valuable Grid pattern in existence:

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
}

Cards get at least 280px, the browser fits as many columns as possible, and everything reflows on resize — zero breakpoints. (This exact pattern lays out the article cards on this site.)

Aligning content across rows: subgrid

The historic Grid weakness — children of children couldn’t align to the outer grid — was solved by subgrid, now supported in all modern engines. The classic use case is equal-height card internals:

.card-grid > .card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid; /* title, body, footer align across ALL cards */
}

Every card’s footer sits on the same line, no matter how long each title is. Before subgrid this required JavaScript or misery.

Overlap without position: absolute

Grid cells can stack. A hero with text over an image:

.hero {
  display: grid;
}
.hero > * {
  grid-area: 1 / 1; /* everything in the same cell */
}
.hero > .caption {
  align-self: end;
}

They’re teammates: the composite pattern

The real answer to “Grid or Flexbox” is usually both, at different levels:

  • Grid builds the macro structure: page shell, card grids, dashboard regions, form layouts with aligned labels.
  • Flexbox handles the micro structure inside each region: the row of buttons in the card footer, the icon-plus-label, the nav links.

A concrete example — a card:

.card {
  display: grid;                 /* Grid: controls vertical regions */
  grid-template-rows: auto 1fr auto;
}
.card-footer {
  display: flex;                 /* Flexbox: distributes footer items */
  justify-content: space-between;
  align-items: center;
}

Once you internalize “Grid for skeletons, Flex for rows,” the decision takes about a second per element.

Decision table for common components

ComponentToolWhy
Navigation barFlexboxOne row, content-sized items, auto margins
Page shell (header/sidebar/main)GridTwo dimensions must agree
Card grid / galleryGridUniform tracks, auto-fill responsiveness
Inside a cardGrid rows + Flex footerPin footer, distribute actions
Form with aligned labelsGridColumns align across rows
Tag/chip listFlexboxNatural sizes, wrapping
Centering one thingEitherplace-items: center (Grid) is shortest
Table-like dataNeitherUse an actual <table> — seriously

That last row is a real recommendation: if the content is tabular data, semantic tables beat both layout systems for accessibility and behave better than you remember.

Performance and practical footnotes

Layout choice is rarely a performance problem, but two notes matter in practice. First, deeply nested flex containers (the “div soup” pattern old frameworks encouraged) make layout recalculation more expensive and markup harder to reason about — Grid usually flattens the tree. Second, animating grid or flex properties triggers layout; animate transform instead, as we covered in the CLS section of our Core Web Vitals guide.

And if you want to see these patterns in a real codebase, the site you’re reading is a working example — the tutorial where we build a blog with Astro uses the auto-fill card grid, the composite card pattern and the Grid page shell verbatim.

Final Thoughts

Grid vs Flexbox stopped being a debate years ago; it’s a division of labor. Flexbox distributes content along a line and is unbeatable for the small stuff. Grid imposes structure in two dimensions and is unbeatable for the big stuff. The modern additions — gap everywhere, auto-fit/minmax, subgrid, dvh units — closed the gaps that used to force hacks.

So the next time your cursor hovers over display:, ask the intent question — does content drive this layout, or does layout drive this content? — write the corresponding property, and move on to problems that deserve your attention.