CSS Flexbox vs Grid: When to Use Each

css flexbox css-grid layout web-design

CSS Flexbox and Grid are both layout systems built into modern browsers, but they solve different problems. Flexbox is a one-dimensional layout model — it handles a row or a column. Grid is two-dimensional — it handles rows and columns simultaneously. Choosing the right one isn’t always obvious, so here’s a direct comparison with real code.

The Core Difference

Flexbox lays out items along a single axis — either a row (left-to-right) or a column (top-to-bottom). The items can wrap onto new lines, but Flexbox doesn’t control alignment between rows.

Grid places items in a defined two-dimensional space. Rows and columns are explicit, and items can span multiple tracks.

A useful mental model: Flexbox works from the content outward — you set rules on a container and the items find their own space. Grid works from the layout inward — you define the structure first, then place content into it.

Flexbox: What It Does Well

Flexbox excels at distributing a set of items along a single axis with space control:

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 24px;
  height: 64px;
}

.navbar .logo {
  flex-shrink: 0; /* logo never shrinks */
}

.navbar .nav-links {
  display: flex;
  gap: 24px;
  list-style: none;
}

justify-content: space-between pushes the logo to the left and the nav links to the right. align-items: center vertically centers both. This is exactly what Flexbox is designed for.

Card Rows with Dynamic Width

When you have a row of cards that should fill available space equally:

.card-row {
  display: flex;
  gap: 16px;
}

.card {
  flex: 1;          /* each card grows equally to fill space */
  min-width: 200px; /* prevents cards from shrinking too far */
}

Flexbox handles this cleanly because you’re controlling one axis — the horizontal distribution.

Centering a Single Item

Centering was notoriously difficult in CSS before Flexbox. Now it’s two lines:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
}

This works for centering modals, hero sections, and loading spinners.

Flex Properties Reference

PropertyApplied ToEffect
justify-contentContainerDistributes items along main axis
align-itemsContainerAligns items along cross axis
flex-wrapContainerAllows items to wrap to new lines
flex-growItemHow much an item grows relative to siblings
flex-shrinkItemHow much an item shrinks when space is tight
flex-basisItemStarting size before grow/shrink applies
orderItemOverride visual order (doesn’t change DOM order)
align-selfItemOverride cross-axis alignment for one item

Grid: What It Does Well

Page-Level Layout

Grid’s two-dimensional control makes it the right tool for overall page structure:

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

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

The sidebar is 240px fixed; the right column fills remaining space (1fr). The header, main content, and footer stack vertically in the right column. This layout requires both row and column control simultaneously — Grid handles it directly; Flexbox would require nested containers.

Responsive Image Galleries

Grid’s auto-fill and minmax() create responsive grids without media queries:

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

This creates as many columns as fit at minimum 200px width. On a 1200px container that’s 6 columns; on 600px it’s 3 columns. No JavaScript, no media queries.

Overlapping Layers

Grid allows multiple items to occupy the same cells, enabling layered designs:

.hero {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 480px;
}

.hero-image,
.hero-text {
  grid-column: 1;
  grid-row: 1; /* both items in the same cell */
}

.hero-text {
  z-index: 1;
  place-self: center;
}

This overlays text on an image without absolute positioning. The items stay in normal flow, which plays better with responsive design.

Dashboard Layouts with Mixed Sizes

.dashboard {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 16px;
}

.widget-wide { grid-column: span 8; }
.widget-narrow { grid-column: span 4; }
.widget-full { grid-column: 1 / -1; } /* spans all 12 columns */

A 12-column grid lets components claim different widths. Flexbox achieves similar results, but explicit column spans in Grid are more predictable when you need specific proportions.

When to Use Flexbox vs Grid: Decision Guide

Is the layout one-dimensional (a row OR a column)?
  → Yes: Flexbox

Is the layout two-dimensional (rows AND columns)?
  → Yes: Grid

Are you centering content inside a container?
  → Flexbox (2 lines: justify-content + align-items)

Do you need items to span across rows or columns?
  → Grid

Is the item count unknown or dynamic?
  → Flexbox (items flow naturally; Grid needs defined tracks)

Is the layout structure page-level (header/sidebar/main)?
  → Grid

Are you building a component (button group, card row, nav)?
  → Flexbox

They Work Together

Flexbox and Grid are not competing tools. The same page typically uses both:

/* Grid handles the page skeleton */
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 64px 1fr;
}

/* Flexbox handles the navbar inside the header */
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Grid handles the card grid inside main */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 24px;
}

/* Flexbox handles layout inside each card */
.card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

The page skeleton uses Grid; each component within it uses Flexbox. This is the standard pattern in modern CSS codebases.

Browser Support

Both Flexbox and Grid have excellent browser support — over 97% global coverage for Flexbox and 96%+ for Grid as of 2025. Subgrid (which lets nested grids inherit parent track sizes) reached broad support in late 2023.

The only Grid feature with limited support is masonry layout, which is still in the experimental stage and not production-ready.

Common Mistakes

Using Flexbox for a 2D grid: developers often nest flex containers to simulate a grid. This works but requires more markup and loses the alignment guarantees that Grid provides across rows.

Using Grid for simple horizontal centering: Grid works here, but Flexbox’s justify-content: center; align-items: center is shorter and more semantic for this use case.

Forgetting gap: both Flexbox and Grid support gap (formerly grid-gap). Using margin on items instead of gap on the container creates edge cases at the start and end of rows.

Not using fr units in Grid: fr (fractional unit) is the key to responsive grids. grid-template-columns: repeat(3, 1fr) creates three equal columns that scale with the container. Hard-coding pixel values kills the responsiveness.

For keeping your CSS clean and performant after building your layout, see the CSS Minification guide. To check and clean your markup, use the HTML Formatter tool.

Responsive Design Patterns

Both Flexbox and Grid handle responsive layouts, but with different approaches.

Flexbox: Wrap-Based Responsiveness

Flexbox wraps items naturally when space runs out, making it well-suited for components where you don’t need to control exact column counts:

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tag {
  flex: 0 1 auto; /* don't grow, can shrink, size from content */
  padding: 4px 12px;
  border-radius: 9999px;
}

Tags flow into as many rows as needed without any media queries.

Grid: Controlled Breakpoints

Grid gives you explicit control at each breakpoint:

.article-grid {
  display: grid;
  grid-template-columns: 1fr;   /* mobile: single column */
  gap: 24px;
}

@media (min-width: 640px) {
  .article-grid {
    grid-template-columns: repeat(2, 1fr);  /* tablet: 2 columns */
  }
}

@media (min-width: 1024px) {
  .article-grid {
    grid-template-columns: repeat(3, 1fr);  /* desktop: 3 columns */
  }
}

The repeat(auto-fill, minmax()) pattern shown earlier eliminates media queries entirely for uniform grids:

.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 24px;
}

Accessibility Considerations

Both layout models affect how content is perceived and navigated by assistive technologies.

Visual vs DOM order: CSS Grid (and Flexbox order) can place items visually in a different order than the DOM. Screen readers follow DOM order, not visual order. If you use grid-column/grid-row placement or order to reorder items visually, verify that the DOM order makes logical sense when read linearly.

Focus order: Keyboard navigation follows DOM order. Reordering items visually with Grid placement can create a confusing tab order for keyboard users. The CSS Working Group recommends keeping DOM order and visual order aligned whenever possible.

Subgrid

CSS Subgrid (now supported in all major browsers) allows nested grid elements to participate in the parent grid’s track definitions. This solves the long-standing problem of aligning content inside cards to each other across a grid:

.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid; /* inherits parent row tracks */
}

/* Now card title, body, and footer align across all cards in the row */
.card-title  { grid-row: 1; }
.card-body   { grid-row: 2; }
.card-footer { grid-row: 3; }

Without subgrid, achieving cross-card alignment required JavaScript to equalize heights. Subgrid makes it a pure CSS solution.

Summary

  • Use Flexbox for one-dimensional layouts: navbars, button groups, card rows, centering
  • Use Grid for two-dimensional layouts: page skeletons, galleries, dashboards, overlapping layers
  • Use both together — Grid for macro layout, Flexbox for micro components
  • Neither is universally better; the right choice depends on whether you need one axis or two

For a complete look at encoding URLs in your CSS (background images, url() values), see the URL Encoding guide.