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-betweenor 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
| Aspect | Flexbox | Grid |
|---|---|---|
| Dimensions | 1D (row or column) | 2D (rows and columns) |
| Content vs layout | Content-driven | Layout-driven |
| Item sizing | Items determine own size | Container defines sizes |
| Column alignment | Items in different rows are independent | Columns aligned across all rows |
| Named areas | No | Yes (grid-template-areas) |
| Gap support | Yes | Yes |
| Wrapping | flex-wrap | Implicit tracks |
| Item placement | Sequential (with order) | Explicit coordinates |
| Overlap | Not easily | Yes (same grid-area) |
| Subgrid | No | Yes |
| Browser support | Excellent | Excellent |
| Learning curve | Lower | Higher |
| Best for | Components | Page layouts |
Common Patterns โ Best Tool
Navigation Bar: Flexbox
.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:
| Concern | Flexbox | Grid |
|---|---|---|
| Layout calculation | Single pass (simple), multiple passes (wrap) | Generally single pass |
| Reflow cost | Low | Low |
| Many items (1000+) | Slightly faster | Slightly slower |
| Nested containers | Each level adds calculation | Subgrid avoids extra calculations |
| Animation | Avoid animating flex properties | Avoid 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
transformandopacityinstead. - 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 |
|---|---|
| Navbar | Flexbox |
| Page layout | Grid |
| Card grid | Grid |
| Button row | Flexbox |
| Form fields | Grid |
| Sidebar + content | Grid |
| Media object | Flexbox |
| Dashboard | Grid |
| Footer links | Flexbox |
| Gallery | Grid |
| Centering | Either |
| Vertical stack | Flexbox |
| Holy grail layout | Grid |
| Toolbar | Flexbox |
| Pricing table | Grid |
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.