Skip to main content

flexbox vs CSS grid comparison

Flexbox handles one-dimensional layouts (a single row or column); CSS Grid handles two-dimensional layouts (rows and columns at the same time).

Theory

TL;DR

  • Flexbox = a single bus aisle: items flow along one axis and shrink or grow to fill space
  • Grid = a city block: you define streets AND avenues, items sit at intersections
  • Main difference: Flexbox distributes space along one axis; Grid controls both axes at once
  • 1D layout (navbar, button group, card row)? Flexbox. Full page or dashboard? Grid
  • They combine well: Grid for the page shell, Flexbox inside each cell

Quick example

Same visual output, different levels of control:

html
<!-- Flexbox: items flow along main axis --> <div class="flex-row"> <div class="item">Nav</div> <div class="item">Logo</div> <div class="item">Login</div> </div> <!-- Grid: tracks defined upfront --> <div class="grid-layout"> <header>Header</header> <aside>Sidebar</aside> <main>Content</main> </div>
css
/* Flexbox: one axis, space distributed between items */ .flex-row { display: flex; justify-content: space-between; align-items: center; } /* Grid: two axes, areas named explicitly */ .grid-layout { display: grid; grid-template-columns: 200px 1fr; grid-template-rows: 60px auto; grid-template-areas: "header header" "sidebar content"; }

Flexbox doesn't know about rows. Grid doesn't care about content flow. That gap is where the real decision lives.

Key difference

Flexbox starts with content and distributes space around it. Items grow, shrink, and wrap, but always along one axis. Grid starts with structure: you define tracks first, then items fill them. That means Grid lets items span multiple rows and columns independently, while Flexbox items always follow the sequence. Nesting a Flexbox inside a Grid cell is common and natural - that combination covers most real-world layouts.

When to use

  • Navbar with logo and menu links: Flexbox (justify-content: space-between)
  • Card row where all cards need equal height: Flexbox (align-items: stretch)
  • Page layout with header, sidebar, content, and footer: Grid
  • Dashboard with asymmetric panels of different sizes: Grid (grid-template-areas)
  • Centering a single element: both work (display: flex; place-items: center or display: grid; place-items: center)
  • Gallery where some items span multiple columns: Grid (grid-column: span 2)
  • Component inside a Grid cell, like a button group in a nav area: Flexbox

Comparison table

AspectFlexboxCSS Grid
Dimensions1D (row or column)2D (rows and columns)
Primary strengthSpace distribution along one axisExplicit item placement and spanning
Container behaviorItems flex to fill available spaceTracks are defined first, items fill them
Item controlflex-grow, flex-shrink, flex-basis, ordergrid-area, grid-column, grid-row, place-self
Alignmentjustify-content, align-items, align-selfjustify-items, align-items, place-items, place-content
Overlapping itemsNot straightforwardNative support via shared grid area
Browser supportIE10+, full modern supportIE11 partial; subgrid since Chrome 117
When to useNavbars, button groups, card rows, centeringPage layouts, dashboards, galleries, print grids

How the browser handles this

When the browser parses display: flex, it creates a flex formatting context. It calculates a hypothetical size for each item using flex-basis, then distributes leftover space by flex-grow factors. Line wrapping only happens when flex-wrap: wrap is set.

display: grid creates a grid formatting context. The browser builds a track map from grid-template-columns and grid-template-rows, then places items either via explicit grid-area declarations or through the auto-placement algorithm. Grid track sizes are resolved once upfront; Flexbox recalculates on each reflow. For large lists (1000+ items), Flexbox tends to be faster because line caching is simpler than grid track resolution.

Common mistakes

Mistake 1: Using flex-direction: column for a sidebar + main layout

css
/* Wrong: flex-direction column just stacks items top to bottom */ .layout { display: flex; flex-direction: column; } /* Fix: Grid creates an actual side-by-side structure */ .layout { display: grid; grid-template-areas: "sidebar main"; grid-template-columns: 240px 1fr; }

This is the most common layout mistake I see in code reviews. Developers who know Flexbox well reach for flex-direction: column when they need columns side by side, and it just stacks vertically instead.

Mistake 2: align-items: center on Grid for full centering

css
/* Wrong: only centers on cross-axis per track, not both axes */ .grid { display: grid; align-items: center; } /* Fix: centers both the block and inline axes */ .grid { display: grid; place-items: center; }

align-items handles the block axis. justify-items handles the inline axis. place-items is the shorthand for both, and it's what you actually want for true centering.

Mistake 3: justify-content: space-between with items of unequal width

css
/* Looks uneven when item widths differ */ .nav { display: flex; justify-content: space-between; } /* Fix: gap gives predictable, consistent spacing */ .nav { display: flex; gap: 1rem; }

space-between distributes free space between items based on available room. When items have different widths, the gaps look inconsistent. gap applies the same spacing regardless of item size, and it doesn't bleed onto outer edges after wrapping.

Mistake 4: Missing min-height: 0 on flex children in a column layout

css
/* Item refuses to shrink, overflows the parent */ .sidebar-item { display: flex; flex-direction: column; /* Missing: min-height: 0 */ } /* Fix */ .sidebar-item { display: flex; flex-direction: column; min-height: 0; /* Allows shrinking below content size */ overflow: auto; }

Flex items default to min-height: auto, which prevents shrinking below content size. In column layouts this causes overflow. min-height: 0 removes that restriction.

Mistake 5: Subgrid without matching parent tracks (Chrome 117+)

css
/* Parent defines only two columns */ .grid-parent { display: grid; grid-template-columns: 1fr 2fr; } /* Subgrid falls back to auto if parent track count does not match */ .grid-child { display: grid; grid-template-columns: subgrid; }

Subgrid inherits parent tracks. If the parent doesn't define the expected track count, subgrid falls back to auto and alignment breaks silently. Define explicit tracks on the parent before using subgrid in children.

Real-world usage

  • React and Next.js: Flexbox for component internals (Chakra UI buttons, navbars); Grid for app shells (Vercel dashboard)
  • Tailwind CSS: flex space-x-4 for inline lists; grid-cols-12 for page scaffolding in Shopify themes
  • Bootstrap 5: .d-flex .justify-content-between runs on Flexbox; complex multi-column forms use Grid
  • Material UI: Flexbox in Card and List components; Grid in data tables

Follow-up questions

Q: What is the difference between justify-content and justify-items?
A: justify-content distributes space between tracks or items at the container level. justify-items aligns each item within its own grid area. Flexbox has justify-content but no justify-items - that property only applies in Grid.

Q: How does flex-basis differ from width?
A: flex-basis is the starting size before flex-grow and flex-shrink are applied. width is the standard box model dimension. When both are set, flex-basis takes priority inside a flex container. Setting flex-basis: 0 makes all items start from zero and grow equally.

Q: How does grid-auto-flow: dense work?
A: When spanning items leave gaps in the grid, dense tells the browser to fill those gaps with smaller items that come later in the DOM. It improves visual density but changes visual order from DOM order, which can break keyboard navigation and screen reader flow.

Q: Why use gap instead of margins on flex or grid children?
A: gap only creates space between items, not on outer edges. Margins on children accumulate and can cause double spacing at container edges or after line wraps. gap handles this correctly in both contexts.

Q: What is subgrid and when do you actually need it?
A: Subgrid lets a nested grid inherit its parent's track sizes. Without it, a card inside a Grid cell defines its own internal tracks, which won't align with cards in adjacent cells. With subgrid, all cards share the same row tracks so their content lines up across columns. Available in Chrome 117+, Firefox 71+, Safari 16+. For older browsers, CSS custom properties on a shared ancestor are a practical fallback.

Examples

jsx
function Navbar() { return ( <nav style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '1rem 2rem', borderBottom: '1px solid #eee' }}> <span style={{ fontWeight: 'bold' }}>Logo</span> <ul style={{ display: 'flex', listStyle: 'none', gap: '2rem', margin: 0 }}> <li>Home</li> <li>About</li> <li>Contact</li> </ul> <button>Login</button> </nav> ); }

Logo on the left, navigation with consistent spacing, login button on the right. The inner ul is also a flex container. This is the "Grid for the shell, Flexbox for internals" pattern working in practice.

Page layout with CSS Grid

css
.app { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: 60px 1fr 40px; grid-template-areas: "header header" "sidebar content" "footer footer"; min-height: 100vh; } header { grid-area: header; } aside { grid-area: sidebar; } main { grid-area: content; } footer { grid-area: footer; }

Named areas make the layout readable at a glance. Change grid-template-columns to 1fr inside a media query and the whole structure reflows automatically.

css
.gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } /* Featured item occupies two columns */ .gallery .featured { grid-column: span 2; }

Item spanning is where Grid has no Flexbox equivalent. You cannot make one flex item occupy two column widths while keeping others in a shared grid. Grid handles this natively.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?