Strategic Software Engineer and Business Problem Solver - Merveille Alexandre
blogbeginner

CSS Grid vs Flexbox: A Practical Guide for 2024

Learn when to use CSS Grid and when to use Flexbox with practical examples and real-world use cases.

January 22, 2024
16 min read
Merveille Alexander

CSS Grid vs Flexbox: A Practical Guide for 2025


The Moment I Wanted to Throw My Laptop Out the Window

I still remember it like it was yesterday. My first job as a junior developer, my first real project, and I was staring at my screen at 11 PM, trying to build a simple card layout.

Three hours earlier, I had been so confident. I knew CSS. I knew Flexbox. I had watched the tutorials, built the little practice layouts, felt ready for anything.

Then the design landed in my inbox.

"Can you make these cards responsive? They should be a grid on desktop, stack on mobile, and the footer should always stick to the bottom. Oh, and the images need to be perfectly aligned. Thanks!"

Simple, right? Three hours later, I had tried everything. I had display: flex here, flex-wrap: wrap there, justify-content and align-items everywhere. Nothing worked the way I wanted. The cards stretched weirdly. The images refused to align. The footer floated in the middle of nowhere like it was lost at sea.

I was this close to becoming a carpenter instead.

If that sounds like you right now—if you're lying in bed at 2 AM wondering why your CSS layout won't behave—this guide is for you. I'm going to explain Grid and Flexbox in a way I wish someone had explained to me back then.


The One-Sentence Rule That Changed Everything

Here's the simplest way to think about it:

Flexbox is for one-dimensional layouts (rows OR columns). Grid is for two-dimensional layouts (rows AND columns).

That's it. That's the whole secret.

Let me show you what I mean:

Code
/* Flexbox: Good at ONE thing at a time */
.container {
  display: flex;
  flex-direction: row; /* OR column, but not both at once */
}

/* Grid: Good at TWO things at once */
.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr; /* Rows AND columns together */
  grid-template-rows: auto 200px;
}

Everything else flows from this simple idea. When you understand this, you'll stop guessing and start knowing which tool to use.


Flexbox: The One-Dimensional Master

Think of Flexbox as a way to distribute items along a single line. It's like arranging books on a shelf—you're only worried about how they sit next to each other, not about the shelves above or below.

When You Should Use Flexbox

Navigation bars and menus

Code
<nav class="navbar">
  <div class="logo">MySite</div>
  <ul class="nav-links">
    <li>Home</li>
    <li>About</li>
    <li>Services</li>
    <li>Contact</li>
  </ul>
  <button class="login">Login</button>
</nav>
Code
.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  background: #333;
  color: white;
}

.nav-links {
  display: flex;
  gap: 2rem;  /* Beautiful, simple spacing */
  list-style: none;
}

.logo {
  font-size: 1.5rem;
  font-weight: bold;
}

.login {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
}

See what happened there? We have three elements that need to sit in a row. One line of display: flex and they arrange themselves perfectly. No floats, no clearfix, no crying.

Card footers that stick to the bottom

This was the problem that haunted me for hours:

Code
<div class="card">
  <img src="image.jpg" alt="Product">
  <h3>Product Title</h3>
  <p>This is a description that might be short or long.</p>
  <button>Buy Now</button>  <!-- This should always be at the bottom -->
</div>

The CSS that saved my life:

Code
.card {
  display: flex;
  flex-direction: column;
  height: 100%;  /* Cards might have different heights */
  padding: 1rem;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.card button {
  margin-top: auto;  /* MAGIC LINE - pushes button to bottom */
  background: #007bff;
  color: white;
  padding: 0.5rem;
  border: none;
  border-radius: 4px;
}

That margin-top: auto in a flex container is pure magic. It pushes the element as far down as possible, making your button always stick to the bottom regardless of how much text is above it.

Centering things (the classic)

Code
.center-me {
  display: flex;
  justify-content: center;  /* horizontal centering */
  align-items: center;      /* vertical centering */
  height: 200px;
  border: 1px solid red;
}

No more position: absolute + transform hacks. No more table-cell tricks. Just three lines of CSS and your element is perfectly centered in both directions.

Flexbox Properties You'll Actually Use

Code
.container {
  /* Put these on the parent */
  display: flex;
  flex-direction: row | row-reverse | column | column-reverse;
  flex-wrap: wrap | nowrap;
  justify-content: flex-start | flex-end | center | space-between | space-around;
  align-items: stretch | flex-start | flex-end | center | baseline;
  gap: 10px;  /* THE BEST THING EVER ADDED TO CSS */
}

.item {
  /* Put these on the children */
  flex-grow: 0 | 1 | 2;  /* Can this item grow? */
  flex-shrink: 0 | 1;    /* Can this item shrink? */
  flex-basis: auto | 100px | 30%;  /* Starting size */
  
  /* The shortcut version (you'll use this 90% of the time) */
  flex: 1;  /* grow and shrink equally */
}

I use gap constantly. It's the best CSS addition in years. No more hacky margins on everything except the last child!


Grid: The Two-Dimensional Powerhouse

If Flexbox is arranging books on a shelf, Grid is designing an entire library floor plan. You decide where the shelves go, how many rows you need, which sections get more space.

When You Should Use Grid

Photo galleries and card grids

Code
<div class="gallery">
  <div class="gallery-item">Photo 1</div>
  <div class="gallery-item">Photo 2</div>
  <div class="gallery-item">Photo 3</div>
  <div class="gallery-item">Photo 4</div>
  <div class="gallery-item">Photo 5</div>
  <div class="gallery-item">Photo 6</div>
</div>
Code
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
  padding: 20px;
}

.gallery-item {
  background: #f0f0f0;
  padding: 20px;
  text-align: center;
  border-radius: 8px;
}

This is the kind of layout that made me want to become a carpenter in 2015. Look at that repeat(auto-fit, minmax(250px, 1fr)) line. It's saying: "Create as many columns as you can fit, each at least 250px wide, and distribute the extra space equally."

On a 1200px screen, you get 4 columns. On a 800px screen, you get 3. On a phone, you get 1. No media queries needed. It's responsive by default.

Magazine-style layouts

Code
<div class="magazine">
  <header class="header">Site Header</header>
  <nav class="sidebar">Navigation</nav>
  <main class="main-content">
    <h1>Article Title</h1>
    <p>Long article content here...</p>
  </main>
  <aside class="sidebar-right">Related Links</aside>
  <footer class="footer">Footer</footer>
</div>
Code
.magazine {
  display: grid;
  grid-template-areas: 
    "header header header"
    "sidebar main right"
    "footer footer footer";
  grid-template-columns: 200px 1fr 200px;
  gap: 20px;
  min-height: 100vh;
}

.header { grid-area: header; background: #333; color: white; padding: 1rem; }
.sidebar { grid-area: sidebar; background: #f0f0f0; padding: 1rem; }
.main-content { grid-area: main; padding: 1rem; }
.sidebar-right { grid-area: right; background: #f0f0f0; padding: 1rem; }
.footer { grid-area: footer; background: #333; color: white; padding: 1rem; }

This is the kind of layout that used to require nested flexboxes, negative margins, and a prayer. Now it's just naming areas and telling Grid where they go.

On mobile, you can completely rearrange it:

Code
@media (max-width: 768px) {
  .magazine {
    grid-template-areas: 
      "header"
      "sidebar"
      "main"
      "right"
      "footer";
    grid-template-columns: 1fr;
  }
}

Same HTML, completely different layout. This is the power of Grid.

Holy Grail Layout (the one everyone asks for)

Code
.holy-grail {
  display: grid;
  grid-template-rows: auto 1fr auto;  /* header takes what it needs, main fills rest, footer sticks */
  min-height: 100vh;
}

header { background: #333; color: white; padding: 1rem; }
main { padding: 2rem; }
footer { background: #333; color: white; padding: 1rem; }

A full-height layout with header at top, footer at bottom, and content filling the middle. Five lines of CSS. In 2010, this was a Stack Overflow question with 50 upvotes and seven different hacky answers.

Grid Properties You'll Actually Use

Code
.container {
  /* On the parent */
  display: grid;
  grid-template-columns: 200px 1fr 2fr | repeat(3, 1fr) | minmax(250px, 1fr);
  grid-template-rows: auto 300px 1fr;
  gap: 20px;
  grid-template-areas: 
    "header header header"
    "sidebar main main"
    "footer footer footer";
  justify-items: stretch | start | center | end;
  align-items: stretch | start | center | end;
}

.item {
  /* On the children */
  grid-column: 1 / 3 | span 2;
  grid-row: 1 / 2;
  grid-area: header;  /* matches grid-template-areas */
  justify-self: stretch | start | center | end;
  align-self: stretch | start | center | end;
}

The fr unit is crucial—it means "fraction of the available space." 1fr takes one share, 2fr takes two shares. It's like flex-grow but for Grid.


The Practical Decision Framework

After years of building layouts, here's my simple decision tree:

Use Flexbox When:

1. You're arranging items in a single line

Code
/* Navigation, button groups, form inputs */
.button-group {
  display: flex;
  gap: 10px;
}

2. You need content-based sizing with wrapping

Code
/* Tags, chips, badges */
.tags-container {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

3. You want to distribute space along one axis

Code
/* Space between, space around, etc. */
.pricing-plans {
  display: flex;
  justify-content: space-around;
}

4. You need to align items on the cross axis

Code
/* Vertically centering items of different heights */
.media-object {
  display: flex;
  align-items: center;
  gap: 15px;
}

Use Grid When:

1. You need rows AND columns simultaneously

Code
/* Card grids, dashboards, image galleries */
.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto 300px auto;
}

2. You want items to overlap

Code
/* Hero sections with text over images */
.hero {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}

.hero-image {
  grid-column: 1;
  grid-row: 1;
}

.hero-text {
  grid-column: 1;
  grid-row: 1;
  z-index: 2;
  /* Text sits on top of image */
}

3. You have explicit column and row relationships

Code
/* Sidebar + main content layouts */
.app-layout {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: auto 1fr auto;
}

4. You need responsive layouts without media queries

Code
.responsive-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  /* Automatically adjusts columns based on available space */
}

Real Examples You'll Actually Build

Example 1: Product Card (Flexbox + Grid Together)

Code
<div class="product-grid">
  <div class="product-card">
    <img src="product1.jpg" alt="Product 1">
    <h3>Product Name</h3>
    <p class="price">$29.99</p>
    <p class="description">Short description here</p>
    <button>Add to Cart</button>
  </div>
  <!-- More cards... -->
</div>
Code
/* Grid for the outer container */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
  padding: 24px;
}

/* Flexbox for the card content */
.product-card {
  display: flex;
  flex-direction: column;
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 16px;
  transition: transform 0.2s;
}

.product-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.product-card img {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 4px;
  margin-bottom: 12px;
}

.product-card h3 {
  margin: 0 0 8px 0;
  font-size: 1.2rem;
}

.product-card .price {
  font-weight: bold;
  color: #007bff;
  margin: 0 0 8px 0;
}

.product-card .description {
  color: #666;
  margin: 0 0 16px 0;
  flex: 1;  /* Pushes button to bottom */
}

.product-card button {
  margin-top: auto;
  background: #007bff;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 4px;
  cursor: pointer;
}

.product-card button:hover {
  background: #0056b3;
}

See how they work together? Grid handles the overall placement of cards, Flexbox handles the internal layout of each card. This is the dream team.

Code
<nav class="navbar">
  <div class="logo">MyBrand</div>
  <ul class="nav-links">
    <li><a href="#">Home</a></li>
    <li><a href="#">Products</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
  <div class="search">
    <input type="text" placeholder="Search...">
    <button>Go</button>
  </div>
</nav>
Code
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
  background: #333;
  color: white;
  flex-wrap: wrap;
  gap: 1rem;
}

.nav-links {
  display: flex;
  list-style: none;
  gap: 2rem;
  margin: 0;
  padding: 0;
}

.nav-links a {
  color: white;
  text-decoration: none;
}

.nav-links a:hover {
  text-decoration: underline;
}

.search {
  display: flex;
  gap: 0.5rem;
}

.search input {
  padding: 0.5rem;
  border: none;
  border-radius: 4px;
}

.search button {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

@media (max-width: 768px) {
  .navbar {
    flex-direction: column;
    align-items: stretch;
  }
  
  .nav-links {
    flex-direction: column;
    align-items: center;
    gap: 1rem;
  }
  
  .search {
    width: 100%;
  }
  
  .search input {
    flex: 1;
  }
}

Example 3: Dashboard Layout

Code
<div class="dashboard">
  <header class="dashboard-header">
    <h1>Dashboard</h1>
    <div class="user-menu">👤 John Doe</div>
  </header>
  
  <aside class="dashboard-sidebar">
    <nav>
      <ul>
        <li>🏠 Home</li>
        <li>📊 Analytics</li>
        <li>📝 Reports</li>
        <li>⚙️ Settings</li>
      </ul>
    </nav>
  </aside>
  
  <main class="dashboard-main">
    <div class="stats-grid">
      <div class="stat-card">Users: 1,234</div>
      <div class="stat-card">Revenue: $12,345</div>
      <div class="stat-card">Orders: 456</div>
      <div class="stat-card">Conversion: 3.2%</div>
    </div>
    
    <div class="chart-container">
      <!-- Chart would go here -->
      <div class="placeholder-chart">📈 Chart Area</div>
    </div>
    
    <div class="recent-activity">
      <h2>Recent Activity</h2>
      <ul>
        <li>User joined - 2 min ago</li>
        <li>Order completed - 15 min ago</li>
        <li>New message - 1 hour ago</li>
      </ul>
    </div>
  </main>
</div>
Code
.dashboard {
  display: grid;
  grid-template-areas: 
    "header header"
    "sidebar main";
  grid-template-columns: 250px 1fr;
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

.dashboard-header {
  grid-area: header;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  background: #333;
  color: white;
}

.dashboard-sidebar {
  grid-area: sidebar;
  background: #f5f5f5;
  padding: 2rem;
}

.dashboard-sidebar ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.dashboard-sidebar li {
  padding: 0.75rem 1rem;
  margin-bottom: 0.5rem;
  border-radius: 4px;
  cursor: pointer;
}

.dashboard-sidebar li:hover {
  background: #e0e0e0;
}

.dashboard-main {
  grid-area: main;
  padding: 2rem;
  background: white;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
}

.stat-card {
  padding: 1.5rem;
  background: #f5f5f5;
  border-radius: 8px;
  text-align: center;
  font-weight: bold;
}

.chart-container {
  margin-bottom: 2rem;
}

.placeholder-chart {
  height: 300px;
  background: #f5f5f5;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: #666;
}

.recent-activity {
  background: #f9f9f9;
  padding: 1.5rem;
  border-radius: 8px;
}

.recent-activity ul {
  list-style: none;
  padding: 0;
}

.recent-activity li {
  padding: 0.75rem 0;
  border-bottom: 1px solid #eee;
}

.recent-activity li:last-child {
  border-bottom: none;
}

@media (max-width: 768px) {
  .dashboard {
    grid-template-areas: 
      "header"
      "sidebar"
      "main";
    grid-template-columns: 1fr;
  }
  
  .dashboard-sidebar ul {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
  }
  
  .stats-grid {
    grid-template-columns: 1fr;
  }
}

Common Beginner Mistakes (I Made All of These)

Mistake 1: Using Grid When You Only Need Flexbox

Code
/* Don't do this */
.button-container {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 10px;
}

/* Do this */
.button-container {
  display: flex;
  gap: 10px;
}

Three buttons in a row? That's a single dimension. Flexbox is simpler, more appropriate, and what other developers expect to see.

Mistake 2: Forgetting About Gap

Old CSS:

Code
/* The old, painful way */
.container {
  display: flex;
}

.container > * {
  margin-right: 20px;
}

.container > *:last-child {
  margin-right: 0;
}

New CSS:

Code
/* The beautiful, simple way */
.container {
  display: flex;
  gap: 20px;
}

gap works in Flexbox, Grid, and even multi-column layouts. Use it. Love it. Never go back.

Mistake 3: Not Using Auto Margins

Code
/* Pushing an item to the right */
.navbar {
  display: flex;
}

.login-button {
  margin-left: auto;  /* MAGIC */
}

Instead of adding spacers or empty divs, let auto margins do the work.

Mistake 4: Overcomplicating Responsive Design

Instead of this:

Code
@media (max-width: 1200px) { /* 3 columns */ }
@media (max-width: 900px) { /* 2 columns */ }
@media (max-width: 600px) { /* 1 column */ }

Do this:

Code
.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

One line, all screen sizes handled.


The 2025 Landscape: What's New and Coming

CSS keeps getting better. Here's what's coming that will make layouts even easier:

Subgrid (Already Here, Finally!)

Code
.parent-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

.child-grid {
  display: grid;
  grid-template-columns: subgrid;  /* Inherits parent columns! */
  grid-column: span 3;
}

This means nested grids can align with their parents perfectly. No more fighting with inconsistent column widths.

Container Queries (Game Changer)

Code
.card-container {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

Components can now respond to their own width, not just the viewport. This is huge for reusable components.

Masonry Layout (Experimental)

Code
.masonry {
  display: masonry;
  masonry-template-tracks: repeat(3, 1fr);
  gap: 20px;
}

Pinterest-style layouts without JavaScript. Still experimental, but coming.


My Simple Workflow for Any Layout

When I sit down to build a new layout, here's what goes through my head:

Step 1: Look at the big picture Is this a page layout with headers, sidebars, footers? That's Grid.

Step 2: Look inside each section Are there items that need to sit in a row or column? That's Flexbox.

Step 3: Check for wrapping Will items need to wrap to new lines on smaller screens? Flexbox with flex-wrap: wrap or Grid with auto-fit.

Step 4: Consider the content Does the content determine the size, or do you need fixed tracks? Flexbox for content-based, Grid for fixed.

Step 5: Add responsiveness Start with mobile, then add min-width media queries. Or use auto-fit and let Grid handle it.


The Truth About Learning CSS Layout

Here's what nobody tells you: you don't need to memorize everything.

I've been doing this for 15 years and I still Google "flexbox cheat sheet" at least once a week. I still forget whether align-items is for the main axis or cross axis. I still look up the exact syntax for minmax.

That's normal. That's fine.

What matters is understanding the concepts:

  • One dimension vs two dimensions
  • Parent properties vs child properties
  • Content-based sizing vs container-based sizing

Everything else is just syntax.


What I'd Tell My Younger Self

If I could go back to that night when I was ready to become a carpenter, here's what I'd say:

Start with the content, not the container. Think about what you're laying out before you think about how to lay it out.

Build something every day. Even if it's just a navbar or a card component. Muscle memory matters.

Use browser DevTools. Right-click, Inspect Element, and play with the values. You can't break anything permanently.

Don't fight the browser. If a layout is fighting you, you're probably using the wrong tool. Take a step back and rethink.

Perfect layouts don't exist. Every design has trade-offs. Good enough ships. Perfect never does.


That dashboard I was trying to build at 11 PM? I finally got it working. The cards aligned, the footer stuck, the images looked beautiful. And when I showed it to my boss the next day, she said "Great, now can you add a sidebar?"

Some things never change. But at least now I know whether to use Grid or Flexbox.


Enjoyed this post? I write about CSS, HTML, and real-world web development every Month. Follow me on X @themarelbiz. And if you have a layout horror story, Message me! We've all been there


Written by Merveille Alexander

Software Engineer sharing knowledge and experience.