CSSintermediate

Flexbox vs Grid: Decision Making

When to use Flexbox, when to use Grid, and when to use both. A practical guide to choosing the right CSS layout system for every scenario.

12 min readยทPublished Mar 29, 2026
cssflexboxgridlayout

The Core Difference

Flexbox is one-dimensional. Grid is two-dimensional. That's the fundamental distinction, but it barely scratches the surface of when to use which.

Flexbox (1D):
Items flow in ONE direction โ€” row or column.

[A] [B] [C] [D] [E]     <- row
or
[A]
[B]                      <- column
[C]

Grid (2D):
Items placed in rows AND columns simultaneously.

[A] [B] [C]
[D] [E] [F]             <- rows and columns aligned
[G] [H] [I]

But real decisions are more nuanced than "1D vs 2D." Both systems can handle many of the same layouts. The question is which one does it better for a given situation.

Content-Driven vs Layout-Driven

This is the most useful mental model for choosing between them.

Flexbox: Content Drives Layout

With flexbox, items determine their own sizes. The layout adapts to the content.

/* Items are as wide as their content, then flex to fill */
.flex-nav {
  display: flex;
  gap: 1rem;
}

.nav-item {
  /* Each item takes exactly the space it needs */
  /* "Home" is narrower than "Documentation" */
}
[Home] [About] [Documentation] [Contact]
 ^^^^   ^^^^^   ^^^^^^^^^^^^^   ^^^^^^^
 each item's width = its content width

Grid: Layout Drives Content

With grid, the container defines the structure. Content fills the predefined slots.

/* Container decides: 4 equal columns */
.grid-nav {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}
[    Home    ] [   About    ] [Documentation] [  Contact   ]
  === 25% ===   === 25% ===   === 25% ===     === 25% ===
  all columns equal regardless of content

The same navigation, but the behavior is fundamentally different:

  • Flexbox: "How wide should each item be? Let the content decide."
  • Grid: "I want 4 equal columns. Content fits inside them."

Decision Tree

What are you building?
|
|-- A row of items (nav, toolbar, buttons)?
|   --> Flexbox
|
|-- A column of items (sidebar list, stack)?
|   --> Flexbox
|
|-- A page layout (header/sidebar/main/footer)?
|   --> Grid
|
|-- A grid of cards that need to align in columns?
|   --> Grid
|
|-- A form with labels and inputs aligned?
|   --> Grid
|
|-- A dashboard with mixed-size widgets?
|   --> Grid
|
|-- Items that need to wrap responsively?
|   |
|   |-- Do items need to align in columns?
|   |   --> Grid (repeat + auto-fit)
|   |
|   |-- Just need to flow and wrap?
|       --> Flexbox (flex-wrap)
|
|-- Centering a single element?
|   --> Either works. Flexbox is simpler.

Side-by-Side Comparisons

Scenario 1: Card Layout

Flexbox approach:

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

.card-flex {
  flex: 1 1 300px;
  max-width: 400px;
}
Row 1: [Card 1  ] [Card 2  ] [Card 3  ]
Row 2: [Card 4  ] [Card 5  ]
                   ^^^^^^^^^^
                   Cards in row 2 DON'T align with columns in row 1

Flexbox wraps items into rows, but items in different rows are independent. Card 4 doesn't align with Card 1's left edge necessarily โ€” it depends on flex-grow.

Grid approach:

.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1rem;
}
Row 1: [Card 1  ] [Card 2  ] [Card 3  ]
Row 2: [Card 4  ] [Card 5  ] [        ]
       ^          ^           ^
       Columns ALIGN across rows

Grid keeps columns aligned. The third cell in row 2 is empty but the grid structure is maintained.

Winner: Grid โ€” when items need to align in both rows and columns.

Scenario 2: Navigation Bar

Flexbox approach:

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

.nav-left {
  display: flex;
  gap: 1rem;
}

Grid approach:

.nav-grid {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
}

Both work. Flexbox is more natural here because:

  • Nav items have variable widths based on content.
  • You want space-between or similar flexible distribution.
  • No need for 2D alignment.

Winner: Flexbox โ€” content-driven, single row, variable widths.

Scenario 3: Form Layout

Flexbox approach:

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

.form-row label {
  flex: 0 0 120px;
  text-align: right;
}

.form-row input {
  flex: 1;
}

Problem: every row is an independent flex container. Labels don't align across rows if their content length varies.

Grid approach:

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

Grid automatically makes the label column as wide as the longest label, consistently across all rows.

Winner: Grid โ€” labels align across rows without hardcoded widths.

Scenario 4: Centering Content

Flexbox:

.center-flex {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

Grid:

.center-grid {
  display: grid;
  place-items: center;
  min-height: 100vh;
}

Both are two lines of actual centering code. Grid's place-items is slightly more concise.

Winner: Tie โ€” both are equally simple.

Scenario 5: Page Layout

Flexbox approach (nested):

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

.page-body {
  display: flex;
  flex: 1;
}

.sidebar { flex: 0 0 250px; }
.main    { flex: 1; }

Grid approach (flat):

.page-grid {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  min-height: 100vh;
}

Grid requires less nesting, is easier to read, and lets you restructure the layout by changing only the grid-template-areas declaration.

Winner: Grid โ€” cleaner, fewer wrappers, easy to restructure.

Comparison Table

AspectFlexboxGrid
Dimensions1D (row or column)2D (rows and columns)
Content vs layoutContent-drivenLayout-driven
Item sizingItems determine own sizeContainer defines sizes
Column alignmentItems in different rows are independentColumns aligned across all rows
Named areasNoYes (grid-template-areas)
Gap supportYesYes
Wrappingflex-wrapImplicit tracks
Item placementSequential (with order)Explicit coordinates
OverlapNot easilyYes (same grid-area)
SubgridNoYes
Browser supportExcellentExcellent
Learning curveLowerHigher
Best forComponentsPage layouts

Common Patterns โ€” Best Tool

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 1rem;
}

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

Flexbox handles variable-width nav items naturally. Items take only the space they need.

Page Layout: Grid

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

Grid gives you the complete page structure in one declaration.

Button Group: Flexbox

.button-group {
  display: flex;
  gap: 0.5rem;
}

.button-group button {
  /* natural width based on text */
}

Buttons have variable text lengths. Flexbox lets each button be as wide as its content.

Dashboard: Grid

.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: minmax(150px, auto);
  gap: 1rem;
}

.widget-large { grid-column: span 2; grid-row: span 2; }
.widget-wide  { grid-column: span 2; }

Dashboard widgets need precise 2D placement. Grid handles this naturally.

Card Inside Cards: Both

/* Grid for the card layout */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
}

/* Flexbox for card internals */
.card {
  display: flex;
  flex-direction: column;
}

.card-body {
  flex: 1; /* pushes footer down */
}

Grid for the overall structure, flexbox for the internal component layout. This is the most common hybrid pattern.

The Hybrid Approach

In practice, most real projects use BOTH. They're not competing โ€” they're complementary.

Pattern: Grid Shell, Flex Components

/* Page structure: Grid */
.app {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: 60px 1fr;
  grid-template-areas:
    "sidebar header"
    "sidebar main";
  min-height: 100vh;
}

/* Header internals: Flexbox */
.header {
  grid-area: header;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 1rem;
}

/* Sidebar navigation: Flexbox */
.sidebar-nav {
  grid-area: sidebar;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 1rem;
}

/* Main content cards: Grid */
.main-content {
  grid-area: main;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
  padding: 1rem;
}

/* Individual card: Flexbox */
.card {
  display: flex;
  flex-direction: column;
}

.card-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: auto;
}

This example uses:

  • Grid for the page shell (2D layout).
  • Flexbox for the header (1D row).
  • Flexbox for the sidebar nav (1D column).
  • Grid for the card container (2D card grid).
  • Flexbox for card internals (1D column).

Pattern: Responsive Layout Switch

Sometimes the same container should switch between flex and grid at different breakpoints:

/* Mobile: simple stack (flexbox) */
.content {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Desktop: structured grid */
@media (min-width: 768px) {
  .content {
    display: grid;
    grid-template-columns: 2fr 1fr;
    grid-template-rows: auto 1fr;
    gap: 1.5rem;
  }
}

Performance Considerations

Both flexbox and grid are highly optimized in modern browsers. Performance differences are negligible for most applications. However:

ConcernFlexboxGrid
Layout calculationSingle pass (simple), multiple passes (wrap)Generally single pass
Reflow costLowLow
Many items (1000+)Slightly fasterSlightly slower
Nested containersEach level adds calculationSubgrid avoids extra calculations
AnimationAvoid animating flex propertiesAvoid animating grid-template

In practice:

  • Don't choose based on performance. Choose based on which system fits the layout better.
  • Avoid animating layout properties in both systems. Animate transform and opacity instead.
  • For very large lists (1000+ items), consider virtualization regardless of layout system.

Migration Strategies

Replacing Floats with Flexbox

/* Old float pattern */
.clearfix::after { content: ''; display: table; clear: both; }
.col-left  { float: left; width: 70%; }
.col-right { float: right; width: 30%; }

/* Modern flexbox */
.row {
  display: flex;
  gap: 1rem;
}
.col-left  { flex: 7; }
.col-right { flex: 3; }

Replacing Flexbox Grids with CSS Grid

/* Flexbox card grid */
.cards {
  display: flex;
  flex-wrap: wrap;
  margin: -0.5rem;
}
.card {
  flex: 0 0 calc(33.333% - 1rem);
  margin: 0.5rem;
}

/* CSS Grid card grid */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}
/* No margin hacks, no calc, cleaner */

Replacing Bootstrap Grid with CSS Grid

/* Bootstrap-style */
.row { display: flex; flex-wrap: wrap; margin: 0 -15px; }
.col-4 { flex: 0 0 33.333%; padding: 0 15px; }
.col-8 { flex: 0 0 66.666%; padding: 0 15px; }

/* Native CSS Grid */
.layout {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 30px;
}
/* No wrapper divs, no negative margins, no padding offsets */

Common Mistakes

Mistake 1: Using Grid for Everything

Not every layout needs grid. A simple row of buttons doesn't need grid-template-columns.

/* Overkill */
.buttons {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.5rem;
}

/* Simpler */
.buttons {
  display: flex;
  gap: 0.5rem;
}

Mistake 2: Using Flexbox for 2D Alignment

When you need items to align in both rows and columns, flexbox fights you.

/* Broken: items in row 2 don't align with row 1 columns */
.cards {
  display: flex;
  flex-wrap: wrap;
}
.card { flex: 1 1 300px; }

/* Fixed: use grid */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

Mistake 3: Deeply Nested Flex Containers

Each flex container creates its own context. Deep nesting makes layouts fragile:

/* Fragile */
.level-1 { display: flex; }
  .level-2 { display: flex; }
    .level-3 { display: flex; }
      .level-4 { display: flex; }

/* Better: use grid to flatten the structure */
.layout {
  display: grid;
  grid-template-areas:
    "a b c"
    "d e f";
}

Mistake 4: Mixing Percentage Widths with Gap

/* BUG: 3 * 33.333% + 2 * 1rem > 100% */
.grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.item { flex: 0 0 33.333%; }

/* FIX: account for gap */
.item { flex: 0 0 calc(33.333% - 0.667rem); }

/* BETTER: just use grid */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

Quick Decision Cheat Sheet

Building...Use
NavbarFlexbox
Page layoutGrid
Card gridGrid
Button rowFlexbox
Form fieldsGrid
Sidebar + contentGrid
Media objectFlexbox
DashboardGrid
Footer linksFlexbox
GalleryGrid
CenteringEither
Vertical stackFlexbox
Holy grail layoutGrid
ToolbarFlexbox
Pricing tableGrid

Key Takeaways

  • Flexbox is for 1D layouts and content-driven sizing. Items flow in one direction and determine their own widths.
  • Grid is for 2D layouts and layout-driven sizing. The container defines the structure, items fill it.
  • Use the decision tree: if items need to align in both rows and columns, use grid. If items just flow in a line, use flexbox.
  • Most real projects use both. Grid for page structure, flexbox for component internals.
  • Don't choose based on performance. The differences are negligible. Choose based on which system fits the layout naturally.
  • When in doubt, ask: "Does the container define the layout, or do the items define the layout?" Container = Grid. Items = Flexbox.

Found this helpful?

Support devsofus โ€” help us keep creating free dev guides.

Related Articles