P
Pixel Show
CSSLayout

CSS Grid vs Flexbox: The Complete 2026 Guide

A deep, practical breakdown of CSS Grid and Flexbox — mental models, every property that matters, real-world patterns, performance notes, and the exact rules for choosing one over the other.

Why This Debate Still Exists

CSS Grid shipped in every major browser in 2017. Flexbox shipped in 2013. Almost a decade later, developers still ask which one they should be using — and the answer is still "it depends." But "it depends" is only useful if you understand what it depends on.

Most articles get this wrong. They tell you Flexbox is for "components" and Grid is for "layouts," then call it a day. That rule works maybe 70% of the time. The other 30% is where real projects live — forms that align labels and inputs across rows, cards whose internals need to stretch to the tallest sibling, dashboards where the sidebar needs to collapse at a specific breakpoint without a media query.

This guide is the version I wish I had when I was trying to stop reaching for Flexbox by reflex. We'll cover the mental models, every property that matters, the real-world patterns, the footguns, and the decision framework I use on every new project in 2026.

The Core Mental Model: 1D vs 2D

If you only remember one thing from this article, make it this:

Flexbox lays items out along a single axis. Grid lays items out along two axes at the same time.

Flexbox is a line of items. You decide the direction (row or column), and Flexbox distributes space along that line. When items don't fit, they either shrink, grow, or wrap onto a new line — but each line is still one-dimensional.

Grid is a table of cells. You define rows and columns up front, and items snap into those cells. Grid can control alignment across both dimensions simultaneously, which is something Flexbox physically cannot do without hacks.

This distinction drives almost every practical decision. A horizontal navigation bar? One dimension. Flexbox. A photo gallery where every row has to line up with every column? Two dimensions. Grid. A sidebar + main content layout where the sidebar has to match the main column's height and both have to align to a header and footer? Two dimensions. Grid.

The "Content vs Layout" Shorthand

A useful follow-up rule: Flexbox works from the content outward. Grid works from the layout inward.

With Flexbox, you hand it a bunch of items and say "figure out how to arrange these." The items decide how much space they need, and Flexbox distributes whatever is left. Add or remove an item, and the layout reshapes automatically. This makes Flexbox ideal when you don't know the number of items ahead of time.

With Grid, you define the tracks first — "I want three columns of equal width and two rows, 200px tall each." Then you place items into that structure. The layout exists before the content does, which is why Grid is better for page-level scaffolding where you care about explicit positioning.

Neither approach is better. They're just different entry points into the same problem.

Browser Support in 2026

Both are rock solid. Flexbox has been baseline since 2015 — you can use every property without a second thought. Grid has been baseline since 2017 and is supported in every browser you will realistically encounter, including every iOS Safari version people still run.

The only features that still require care in 2026 are:

  • subgrid — universal support since early 2023 (Safari, Chrome, Firefox, Edge). Safe to use on the public web. If you support ancient enterprise browsers, check Can I Use.
  • Masonry layout via Grid — still experimental at the time of writing. Firefox supports it behind a flag. Do not ship it to production yet.
  • align-content on Flexbox single-line containers — changed behavior in the L2 spec; shipped in Chrome 123 and Safari 17.6. Old browsers ignore it silently, which is usually fine.
Everything else in this guide works everywhere, today.

Flexbox Deep Dive

Flexbox has two sets of properties: the ones you put on the container (the parent) and the ones you put on the items (the children). Confusing them is the most common source of "why isn't this working" questions on Stack Overflow.

Container Properties

Here is the minimum set of container properties you need to have memorized.

.container {
  display: flex;              / or inline-flex /
  flex-direction: row;        / row | row-reverse | column | column-reverse /
  flex-wrap: nowrap;          / nowrap | wrap | wrap-reverse /
  justify-content: flex-start;/ main axis alignment /
  align-items: stretch;       / cross axis alignment /
  align-content: stretch;     / multi-line cross axis alignment /
  gap: 0;                     / space between items, row and column /
}

flex-direction sets the main axis. In row, the main axis is horizontal and the cross axis is vertical. In column, they swap. This matters because justify-content always targets the main axis and align-items always targets the cross axis — if your flex-direction changes, their meaning changes too.

flex-wrap controls what happens when items don't fit. The default is nowrap, which forces items to shrink. Switch to wrap and items that can't fit move to a new line. Almost every responsive Flexbox layout you write should use wrap.

justify-content distributes extra space along the main axis. The useful values:

  • flex-start — pack items at the start (default)
  • flex-end — pack items at the end
  • center — pack items in the middle
  • space-between — first item at start, last at end, equal gaps between
  • space-around — equal space around each item (half-sized gaps at the edges)
  • space-evenly — equal space everywhere, including edges
align-items aligns items along the cross axis. stretch (default) makes all items the same size in that direction — this is why your flex children all end up the same height in a row layout unless you override it. The other values work the same as justify-content but on the cross axis.

gap is the property you should be reaching for instead of margins. It adds space between items without adding space at the container edges. This is so much cleaner than the old margin-right + :last-child pattern that there is no good reason to write that code anymore.

Item Properties

.item {
  flex-grow: 0;       / how much the item grows if there is extra space /
  flex-shrink: 1;     / how much it shrinks if there is not enough space /
  flex-basis: auto;   / the starting size before grow/shrink /
  flex: 0 1 auto;     / shorthand for the three above /
  order: 0;           / reorder without changing HTML /
  align-self: auto;   / override align-items for a single item /
}

The flex shorthand is the one you should actually learn. Three common values cover 90% of cases:

  • flex: 1 — grow to fill available space, shrink if needed, ignore intrinsic size. This is the "fill the rest of the row" value.
  • flex: none — don't grow, don't shrink, use intrinsic size. Use this for fixed sidebars or icons that shouldn't squish.
  • flex: 0 0 300px — don't grow, don't shrink, start at exactly 300px. Use this for fixed-width panels.
The confusing one is flex: auto, which expands to 1 1 auto — grow and shrink, but based on content size, not available space. It gives you "soft" sizing that respects what the item naturally wants to be. Useful for nav bars where you want each item to take as much room as its label needs, but still fill the row.

The Flexbox Footguns

A few things that trip up almost everyone:

  • Percentages on flex items are tricky. flex-basis: 50% does not always mean "half the container" because grow and shrink can override it. If you need exact halves, use flex: 1 1 0 on both items and let flex-grow do the math.
  • min-width: 0 saves you. Flex items have an implicit min-width: auto, which means they refuse to shrink below their content's intrinsic size. This breaks layouts with long words or white-space: nowrap. Add min-width: 0 to the flex item and it will shrink properly.
  • flex-basis: 0 vs flex-basis: 0%. These behave differently in edge cases involving auto-sized containers. Prefer 0 unless you have a specific reason for 0%.
  • Nested flex containers inherit nothing. If you wrap a flex container in another flex container, the inner one does not magically know about the outer one's axis. You have to set flex-direction again.

Grid Deep Dive

Grid has more properties than Flexbox because it's solving a harder problem. The good news is that 80% of real-world Grid usage comes down to three properties: grid-template-columns, gap, and the occasional grid-column on a specific item.

The Essential Container Properties

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); / three equal columns /
  grid-template-rows: auto;              / rows size to content /
  gap: 1rem;                             / both row and column gap /
  justify-items: stretch;                / horizontal alignment of items inside cells /
  align-items: stretch;                  / vertical alignment of items inside cells /
  justify-content: start;                / horizontal alignment of the entire grid /
  align-content: start;                  / vertical alignment of the entire grid /
}

grid-template-columns is where most of the magic happens. You can write it a dozen different ways:

/ Fixed columns /
grid-template-columns: 200px 1fr 200px;

/ Equal columns / grid-template-columns: repeat(3, 1fr);

/ Responsive columns without media queries / grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));

/ Named lines for complex layouts / grid-template-columns: [sidebar-start] 250px [sidebar-end main-start] 1fr [main-end];

The fr unit is critical. It means "one fraction of the available space after fixed sizes are subtracted." 1fr 2fr 1fr creates three columns where the middle one is twice as wide as the outer two.

The One Line That Changes Everything

If you only learn one Grid pattern, learn this:

.responsive-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

This creates a responsive card grid that automatically fits as many 280px+ cards per row as possible, then stretches them to fill the row. No media queries, no JavaScript, no breakpoints. It works from 320px mobile screens to 4K monitors.

auto-fill vs auto-fit is the subtle bit:

  • auto-fit collapses empty tracks, stretching existing items to fill the row
  • auto-fill keeps empty tracks reserved, so items stay at their minmax minimum
For most card grids, auto-fit is what you want. If you're building something that needs consistent item widths regardless of how many items exist, use auto-fill.

Grid Areas — Layouts That Read Like ASCII Art

.layout {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: 80px 1fr 60px;
  grid-template-areas:
    "header header header"
    "sidebar main aside"
    "footer footer footer";
  min-height: 100vh;
  gap: 1rem;
}

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

This is the "holy grail" layout in nine lines. You can literally see the layout by reading the template string. To change the layout at a breakpoint, you redefine grid-template-areas in a media query and items snap to their new positions with zero other changes.

Placing Items Explicitly

Sometimes you need one item to span multiple cells.

.hero {
  grid-column: 1 / -1;   / span from the first line to the last /
  grid-row: 1 / 3;       / span rows 1 and 2 /
}

.featured { grid-column: span 2; / span 2 columns from wherever the item is placed / }

The -1 trick — "end at the last line" — is extremely handy for full-width elements inside a structured grid.

Subgrid

Subgrid lets a nested grid inherit tracks from its parent. This solves one of the most annoying problems in card layouts: when you want every card's headline, body, and footer to align with the equivalent sections in every other card, regardless of content length.

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
}

.card { display: grid; grid-template-rows: subgrid; / inherits rows from .cards / grid-row: span 3; }

Before subgrid, you had to fake this with fixed heights or JavaScript measurements. Now it just works.

Real-World Patterns

Rules are fine. Patterns are better. Here are the ones I use constantly.

Pattern 1: Navigation Bar

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 2rem;
}

.nav-links { display: flex; gap: 1.5rem; }

Flexbox, one dimension, content-driven. Logo on the left, links on the right, everything vertically centered. Classic.

Pattern 2: Card Grid

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

Grid, two dimensions, layout-driven. Responsive by default. This is the one you will write a thousand times.

Pattern 3: Sidebar Layout

.app {
  display: grid;
  grid-template-columns: 260px 1fr;
  min-height: 100vh;
}

Grid. You could do this with Flexbox, but Grid is cleaner because the sidebar width is explicit and the main column always fills the rest. If you want the sidebar to collapse on narrow screens:

@media (max-width: 768px) {
  .app {
    grid-template-columns: 1fr;
  }
}

Pattern 4: Form with Aligned Labels

.form {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1rem 1.5rem;
  align-items: center;
}

Labels auto-size to the widest one, inputs fill the rest. Every row lines up perfectly. Try doing this with Flexbox and you'll end up fighting yourself.

Pattern 5: Stacked Layout With Header and Footer

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.page-main { flex: 1; }

Flexbox wins here because you only care about one axis (vertical) and you want the middle section to absorb all available space. The flex: 1 on the main element is the key — it grows to fill whatever the header and footer don't use.

Pattern 6: Perfect Centering

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

/ Grid version / .center { display: grid; place-items: center; }

Both work. Grid's place-items: center is shorter. Flexbox is marginally more common in existing codebases. Pick whichever your team reads faster.

Combining Both (Where It Gets Fun)

Here's the secret most beginners miss: you don't have to choose. In a real project you use Grid for the page scaffolding and Flexbox for the component internals.

.page {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: 60px 1fr;
  grid-template-areas:
    "header header"
    "sidebar main";
}

.header { grid-area: header; display: flex; align-items: center; justify-content: space-between; padding: 0 1.5rem; }

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

.card-footer { margin-top: auto; display: flex; justify-content: space-between; align-items: center; }

The outer grid positions the three major regions. The header uses Flexbox to distribute its inline contents. The card uses Flexbox in column mode, with margin-top: auto on the footer to push it to the bottom regardless of card content. None of these techniques fight each other because they operate at different levels.

Performance — Does It Actually Matter?

In 99% of projects, no. Both Flexbox and Grid are implemented in native C++ inside the browser's layout engine. They are orders of magnitude faster than anything you could write in JavaScript.

That said, there are a few edge cases where layout cost becomes measurable:

  • Huge Flexbox containers that trigger re-layout on every child change. If you have a flex container with 10,000 children and you're adding or removing them in quick succession, you might notice frame drops. Virtualized lists (React Virtual, TanStack Virtual) avoid this entirely.
  • Grid layouts with complex auto sizing. Grid with grid-template-columns: repeat(auto-fit, minmax(...)) has to do more math than a grid with fixed columns. For static layouts this is irrelevant. For layouts where the grid recalculates on every scroll, it can add up.
  • Deeply nested flex containers. Every level of nesting adds a layout pass. Five levels is fine. Fifteen levels is asking for trouble — not because flex is slow, but because everything is slow when you nest fifteen levels of anything.
If you're worried about layout performance, open DevTools → Performance → look at the "Layout" and "Recalculate Style" bars. If those aren't showing up during your interactions, you don't have a problem.

Accessibility Notes

Two things to keep in mind:

1. order and flex-direction: row-reverse break the reading order for screen readers. Visual order and DOM order should match. If you reorder with CSS, assistive technology still reads the DOM order, which is confusing. Use order sparingly and only when you're sure it won't confuse keyboard navigation.

2. Grid's auto-placement can create the same problem. If you use grid-auto-flow: dense, items get packed into the earliest available cell, which may change their visual order relative to the DOM. Same warning applies.

The rule of thumb: if a sighted user and a keyboard user would experience the content in different orders, your layout is probably lying about the structure.

The Decision Framework

Here is the exact checklist I run through when starting any new layout.

Start with Grid if:

  • You need to align items across two axes at the same time
  • You have a fixed number of named regions (header, sidebar, main, footer)
  • You want responsive behavior without media queries using auto-fit + minmax
  • You need overlapping elements (Grid handles this; Flexbox needs position: absolute)
  • You want form rows where labels line up across every row
Start with Flexbox if:
  • You're laying out a single row or column of items
  • The number of items is dynamic and unknown at build time
  • Content-driven sizing matters more than structural precision
  • You want a sticky footer on a page (flex column + flex: 1 on main)
  • You're arranging items inside a component (nav, card, toolbar, button group)
Use both when:
  • Your outer layout is structured and your inner components are content-driven
  • You have a grid of cards where each card is internally a flex column
  • You have a grid layout with a header that needs Flexbox-style space-between
When in doubt, try Grid first. Nine times out of ten, when I rewrite old Flexbox code, it's because someone reached for Flexbox and then fought it into becoming a two-dimensional layout. Grid would have been shorter from the start.

Common Mistakes I Still See in 2026

  • Using Grid for a simple horizontal nav. Flexbox is shorter and reads better. Save Grid for when you need it.
  • Not using minmax() in Grid. minmax(250px, 1fr) is the single most useful Grid function and half the Grid tutorials out there never mention it.
  • Margin hacks instead of gap. margin-right on every child except the last is legacy code. Use gap. It works in both Flexbox and Grid now.
  • Forgetting that flex: 1 collapses basis to 0. If you want "fill remaining space plus the content's natural size," use flex: auto instead.
  • Nesting Flexbox to fake Grid. If you catch yourself writing a .row containing three .col-4 divs, you're rebuilding Bootstrap on top of Flexbox when Grid would do the same thing in three lines.
  • Ignoring subgrid. It solved one of the most painful alignment problems in CSS and many developers still don't know it exists.

TL;DR

  • Flexbox is one-dimensional. Grid is two-dimensional. That single fact settles most debates.
  • Flexbox is content-first. Grid is layout-first. Use whichever matches your mental model for the current problem.
  • grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)) is the most useful line of CSS in the last decade. Memorize it.
  • gap works in both. Stop using margin hacks.
  • Use Grid for the page scaffold. Use Flexbox for the component internals. Combining them is not cheating — it's the correct answer.
  • If Flexbox code starts fighting you, that's your cue to switch to Grid. If Grid code feels overkill, drop back to Flexbox.
  • Both are performant, accessible (if you respect DOM order), and supported everywhere that matters in 2026.
Stop treating Grid and Flexbox as rivals. They're the two best tools CSS has shipped in twenty years, and they were always designed to work together.

Build websites faster

Staxl — premium templates for modern web projects

Explore Staxl →