Flexbox vs CSS Grid: When to Use Each Layout Method (With Code Comparisons)

Quick answer: use Flexbox when you are arranging items along a single line (a row or a column), and use CSS Grid when you need to control rows and columns at the same time. That is the whole rule in one sentence, but it only clicks once you see the same layout built both ways.

So that is exactly what this article does. We take three layouts that show up in almost every real project, a card row, a holy grail page and a form, build each one twice (once in Flexbox, once in Grid), and score the result. No theory dumps, just side-by-side code and a verdict.

Flexbox vs Grid: the 60-second comparison

Aspect Flexbox CSS Grid
Dimensions One (row or column) Two (rows and columns)
Starting point Content first: items size themselves, the container reacts Layout first: you define the tracks, items drop into them
Item control flex-grow, flex-shrink, flex-basis grid-column, grid-row, named areas
Overlapping items Not natively Yes, items can share cells
Alignment across both axes Per line only Across the whole grid
Best for Navbars, toolbars, button groups, tag lists, centering Page skeletons, dashboards, image galleries, label/field grids
gap support Yes Yes

One myth worth killing right away: Grid did not replace Flexbox. As of 2026 both are baseline features in every browser people actually use, and production codebases use them together on the same page, often on the same component tree.

css grid layout

Layout 1: The card row

Three to six product cards sitting in a row, wrapping on smaller screens. This is the single most common layout on the web, and it is where the Flexbox vs Grid debate gets loudest.

The markup (identical for both)

<ul class="card-row">
  <li class="card">Card 1</li>
  <li class="card">Card 2</li>
  <li class="card">Card 3</li>
  <li class="card">Card 4</li>
  <li class="card">Card 5</li>
</ul>

Version A: Flexbox

.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
}

.card {
  /* grow, shrink, ideal width */
  flex: 1 1 260px;
}

What happens: each card wants to be 260px, grows to fill leftover space, and wraps when there is no room. It works, but notice the side effect. If the last row has one card, that card stretches to 100% width. Sometimes that is what you want. Usually it is not.

The classic workaround is ugly:

/* fake a fixed column count in flexbox */
.card {
  flex: 0 1 calc(33.333% - 1rem);
}

Now you are doing arithmetic to fight the gap, and you need a media query for every breakpoint.

Version B: CSS Grid

.card-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
}

Three lines. No media queries. No calc(). Every card stays in an equal column, rows line up perfectly, and the orphan card in the last row keeps its column width instead of ballooning.

auto-fit vs auto-fill (the detail people miss)

  • auto-fit: empty tracks collapse, so the existing items stretch to fill the row.
  • auto-fill: empty tracks are kept, so 2 cards in a 4-column container stay at 25% width each and sit on the left.

Verdict: Grid wins

Flexbox Wins only if you want content-driven widths, for example a row of tags or filter pills of different lengths, or if you deliberately want the last item to fill the row.
Grid Wins for uniform cards, product listings and galleries. auto-fit plus minmax() is a responsive grid with zero breakpoints.

Pro tip: use both. Grid for the outer card row, Flexbox inside each card to push the price and button to the bottom:

.card {
  display: flex;
  flex-direction: column;
}

.card .cta {
  margin-top: auto; /* pins the button to the bottom */
}
css grid layout

Layout 2: The holy grail page

Header, left nav, main content, right sidebar, footer, with the footer stuck to the bottom on short pages. This layout broke the web for a decade. Both tools can do it now, but not with the same effort.

The markup for Grid

<body class="page">
  <header>Header</header>
  <nav>Nav</nav>
  <main>Main</main>
  <aside>Aside</aside>
  <footer>Footer</footer>
</body>

Version A: CSS Grid

.page {
  display: grid;
  min-height: 100vh;
  gap: 1rem;
  grid-template-columns: 200px 1fr 240px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "nav    main   aside"
    "footer footer footer";
}

header { grid-area: header; }
nav    { grid-area: nav; }
main   { grid-area: main; }
aside  { grid-area: aside; }
footer { grid-area: footer; }

@media (max-width: 768px) {
  .page {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "nav"
      "main"
      "aside"
      "footer";
  }
}

The CSS is a picture of the page. You can read the layout out loud. Changing the mobile order is a matter of rewriting five strings, and no HTML changes.

Version B: Flexbox

Flexbox cannot describe rows and columns at once, so you need an extra wrapper element in the HTML:

<body class="page">
  <header>Header</header>
  <div class="middle">
    <nav>Nav</nav>
    <main>Main</main>
    <aside>Aside</aside>
  </div>
  <footer>Footer</footer>
</body>
.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.middle {
  display: flex;
  flex: 1;
  gap: 1rem;
}

nav   { flex: 0 0 200px; }
main  { flex: 1; }
aside { flex: 0 0 240px; }

@media (max-width: 768px) {
  .middle { flex-direction: column; }
  nav, aside { flex: 0 0 auto; }
}

It works. It is also two nested flex contexts, an extra div that exists purely for layout, and a source order that now dictates your mobile order unless you start juggling the order property (which creates accessibility problems when the visual order and the DOM order disagree).

Verdict: Grid wins, decisively

  • Fewer DOM nodes. Grid needs no layout-only wrapper.
  • Readable intent. grid-template-areas documents itself.
  • Real reordering. Areas move blocks without touching source order semantics for the top level regions.
  • Equal-height columns for free. The 1fr row handles it.

Use Flexbox inside those regions: the header bar (logo left, nav right, justify-content: space-between) and the footer link row are perfect one-dimensional jobs.

Layout 3: The form

Forms are where the answer splits, because a form is really two different layout problems wearing one coat: aligned label and field pairs (two-dimensional) and fields sitting side by side on one line (one-dimensional).

Case 3A: Labels aligned in a column next to inputs

<form class="form">
  <label for="name">Full name</label>
  <input id="name" type="text">

  <label for="email">Email address</label>
  <input id="email" type="email">

  <label for="msg">Message</label>
  <textarea id="msg"></textarea>

  <button class="full" type="submit">Send</button>
</form>

Grid version:

.form {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.75rem 1rem;
  align-items: center;
}

.form .full {
  grid-column: 1 / -1; /* full width row */
  justify-self: start;
}

The label column sizes itself to the longest label and every input starts on the same vertical line. That alignment is the definition of a two-dimensional relationship: item 3 in row 2 has to agree with item 1 in row 1.

Flexbox version:

.form { display: flex; flex-direction: column; gap: 0.75rem; }

.row {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.row label {
  flex: 0 0 140px; /* magic number, guessed by hand */
}

.row input { flex: 1; }

To make it work you need a wrapper div per row and a hard-coded label width. Translate the site into German, and your 140px guess breaks. Flexbox rows do not know about each other, and that is the entire problem.

Case 3B: Two fields on one line, wrapping on mobile

Flexbox version:

.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.field {
  flex: 1 1 220px; /* wraps on its own, no media query */
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

City and postal code sit together, then stack below 220px of available space, without a single breakpoint. A Grid version with repeat(auto-fit, minmax(220px, 1fr)) would also work, but the moment one field should be wider than the other (say a 2:1 ratio for street and number), Flexbox handles it with flex: 2 versus flex: 1 while Grid needs explicit track definitions per breakpoint. For the wider picture, see Difference between CSS Grid and Flexbox.

Verdict: a genuine split

Form need Winner Why
Labels aligned in a column Grid max-content column adapts to any language, no wrappers
Stacked labels above inputs Flexbox A field is just a tiny column, gap does the rest
Fields sharing a line with uneven ratios Flexbox flex-grow ratios beat rewriting track lists
Complex multi-column form sections Grid Named areas and grid-column: span 2 for wide fields
Button bar at the bottom Flexbox justify-content: flex-end with a gap, done
css grid layout

The scoreboard

Layout Winner Deciding factor
Card row Grid Rows must align with each other, orphan items must not stretch
Holy grail page Grid Rows and columns defined together, no wrapper divs
Form Both Grid for cross-row alignment, Flexbox for single-line field groups

Five mistakes we see in real codebases

  1. Using Grid for a navbar. A logo on the left and links on the right is one line. display: flex; justify-content: space-between; is the answer.
  2. Using Flexbox with percentage widths and calc() for card grids. If you are subtracting gap values by hand, you should be using Grid.
  3. Reaching for the order property to fix mobile. It changes visual order but not tab order, which hurts keyboard and screen reader users. Grid areas or a different DOM structure are safer.
  4. Forgetting min-width: 0. Flex and grid items default to a minimum content size, so long strings and tables blow out the layout. min-width: 0 (or minmax(0, 1fr) in Grid) fixes it.
  5. Ignoring subgrid. When cards contain a title, text and button that should line up across cards, grid-template-rows: subgrid on the card aligns inner rows to the parent grid. It is supported across all major browsers now, so there is no reason to fake it with fixed heights.
css grid layout

The decision rule (memorise this one)

Ask a single question before you write a line of CSS:

Do the items only need to line up along one axis, or do they need to line up with items in other rows and columns too?

  • One axis, content decides the sizes: use Flexbox. Navbars, toolbars, tag lists, button groups, a label stacked above an input, pushing an element to the bottom of a card.
  • Two axes, layout decides the sizes: use CSS Grid. Page skeletons, card and product grids, dashboards, aligned label/field forms, anything where row 2 must agree with row 1.

And the practical version for day-to-day work: Grid for the page and the sections, Flexbox for the contents of a component. Most well-built pages nest one inside the other several times over, and that is not a compromise, it is the intended way to use both specs.

FAQ

Is Flexbox still relevant?

Yes. Grid did not deprecate anything. Flexbox is still the best tool for one-dimensional distribution, for content-driven sizing where you do not know item widths in advance, and for wrapping rows of unequal items. Look at any modern component library and you will find Flexbox everywhere inside buttons, chips, list items and headers.

Can I mix Flexbox and Grid?

Absolutely, and you should. A grid item can be a flex container, and a flex item can be a grid container. A very common pattern is a Grid card gallery where each card is a flex column so the call to action sticks to the bottom regardless of text length.

How do I make a grid with Flexbox?

You can, using flex-wrap: wrap and flex: 1 1 <basis>, but you are simulating columns rather than defining them. Rows will not align across the container, and the last row behaves differently from the others. If you need a true grid, grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) gets you there in one declaration.

Why use Flexbox in CSS at all if Grid is more powerful?

Because power is not the goal, expressing intent with the least code is. For a row of three buttons, Flexbox is one property. Grid would need track definitions you would have to update every time a button is added or removed. Flexbox lets the content lead, Grid makes the content obey. See simplilearn.com for their take.

Which one is better for responsive design?

Both reduce media queries, in different ways. Flexbox does it through flex-wrap plus a sensible flex-basis. Grid does it through auto-fit, minmax() and redefining grid-template-areas at breakpoints. Pair either one with container queries and you can build components that adapt to their own space rather than the viewport.

Which has better browser support?

In 2026 this is a non-issue for both. Flexbox and Grid, including gap in Flexbox and subgrid, are supported in all current browsers. The only caution is very old enterprise environments, where the older -ms- grid syntax behaved differently. Check your analytics before you worry about it.

What about Bootstrap’s grid system?

Bootstrap’s grid is built on Flexbox with a 12-column convention baked into class names. It is fine, but it adds markup (rows and columns wrappers) that native CSS Grid makes unnecessary. If you are starting fresh, native Grid gives you the same result with less HTML and no framework dependency.

Wrapping up

Flexbox vs Grid is not a competition with a single winner. Three layouts, six builds, and the score came out Grid, Grid, and a tie, which is roughly the ratio you will see in real projects: Grid handles the skeleton and anything with a two-dimensional relationship, Flexbox handles the inside of components and everything that lives on one line.

Pick with the one-dimensional versus two-dimensional question, nest them without guilt, and you will write less CSS than you did last year.