CSS Grid vs Flexbox: When to Use Each for Perfect Layouts
Master CSS layout techniques by understanding when to use Grid versus Flexbox. Learn through practical examples, real-world use cases, and a decision framework to choose the right tool for every situation.
One of the most common questions I get from developers is: "Should I use CSS Grid or Flexbox?" After building numerous responsive layouts for projects like Liquidity Hunters and this portfolio site, I've developed a clear mental model for choosing between them.
The short answer: use Flexbox for one-dimensional layouts and Grid for two-dimensional layouts. But there's much more nuance to explore.
The Fundamental Difference
Flexbox is designed for one-dimensional layouts - either a row OR a column.
CSS Grid is designed for two-dimensional layouts - rows AND columns simultaneously.
/* Flexbox - items flow in one direction */
.flex-container {
display: flex;
flex-direction: row; /* or column */
}
/* Grid - items placed in rows and columns */
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto auto;
}
But this distinction only scratches the surface. Let's dive deeper.
Flexbox: When Content Size Drives Layout
Flexbox excels when you want content to determine layout size. Items flex and shrink based on their content.
Navigation Menus
/* Perfect Flexbox use case: horizontal navigation */
.nav {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.nav-links {
display: flex;
gap: 2rem;
}
.nav-link {
padding: 0.5rem 1rem;
/* Each link takes only the space it needs */
}
/* Logo on left, links in center, CTA on right */
.nav {
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-center {
display: flex;
gap: 2rem;
}
<nav class="nav">
<div class="nav-logo">Logo</div>
<div class="nav-center">
<a class="nav-link" href="#">Home</a>
<a class="nav-link" href="#">About</a>
<a class="nav-link" href="#">Services</a>
</div>
<button class="nav-cta">Contact</button>
</nav>
Card Components with Variable Content
/* Card with variable content heights */
.card {
display: flex;
flex-direction: column;
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
}
.card-image {
aspect-ratio: 16 / 9;
object-fit: cover;
}
.card-content {
display: flex;
flex-direction: column;
padding: 1.5rem;
flex: 1; /* Takes remaining space */
}
.card-title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.card-description {
flex: 1; /* Pushes footer down */
margin-bottom: 1rem;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto; /* Stays at bottom regardless of content */
}
Centering Content (The Classic Use Case)
/* Perfect vertical and horizontal centering */
.centered-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
/* Center with additional spacing */
.hero {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2rem;
text-align: center;
padding: 4rem 2rem;
}
Dynamic Button Groups
/* Buttons that adapt to content */
.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.button {
padding: 0.75rem 1.5rem;
/* Each button sizes to its label */
}
/* Equal-width buttons when needed */
.button-group--equal .button {
flex: 1;
min-width: 120px;
}
CSS Grid: When Layout Drives Content
Grid shines when you have a specific layout structure in mind and content should fit within it.
Page Layouts
/* Classic holy grail layout */
.page-layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
grid-template-columns: 250px 1fr 200px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
/* Responsive adjustment */
@media (max-width: 768px) {
.page-layout {
grid-template-areas:
"header"
"main"
"sidebar"
"aside"
"footer";
grid-template-columns: 1fr;
}
}
Dashboard Grids
/* Dashboard with multiple panels */
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: auto;
gap: 1.5rem;
padding: 1.5rem;
}
.dashboard-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* Cards spanning multiple columns/rows */
.dashboard-card--wide {
grid-column: span 2;
}
.dashboard-card--tall {
grid-row: span 2;
}
.dashboard-card--featured {
grid-column: span 2;
grid-row: span 2;
}
/* Responsive dashboard */
@media (max-width: 1024px) {
.dashboard {
grid-template-columns: repeat(2, 1fr);
}
.dashboard-card--wide {
grid-column: span 2;
}
}
@media (max-width: 640px) {
.dashboard {
grid-template-columns: 1fr;
}
.dashboard-card--wide,
.dashboard-card--featured {
grid-column: span 1;
grid-row: span 1;
}
}
Image Galleries
/* Responsive image gallery */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
.gallery-item {
aspect-ratio: 1;
overflow: hidden;
border-radius: 8px;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
/* Masonry-style with varying heights */
.gallery--masonry {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-auto-rows: 10px;
gap: 1rem;
}
.gallery--masonry .gallery-item {
/* Use JavaScript to set grid-row-end based on content height */
}
Form Layouts
/* Two-column form layout */
.form {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* Full-width fields */
.form-group--full {
grid-column: span 2;
}
/* Labels and inputs stack with Flexbox within Grid cells */
.form-group label {
font-weight: 500;
}
.form-group input,
.form-group textarea,
.form-group select {
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
}
@media (max-width: 640px) {
.form {
grid-template-columns: 1fr;
}
.form-group--full {
grid-column: span 1;
}
}
Combining Grid and Flexbox
The real power comes from using both together. Here's a complete component example:
/* Blog post card grid with flexbox cards */
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 2rem;
}
.post-card {
display: flex;
flex-direction: column;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.post-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}
.post-card-image {
aspect-ratio: 16 / 9;
object-fit: cover;
}
.post-card-content {
display: flex;
flex-direction: column;
padding: 1.5rem;
flex: 1;
}
.post-card-meta {
display: flex;
gap: 1rem;
margin-bottom: 0.75rem;
font-size: 0.875rem;
color: #6b7280;
}
.post-card-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
line-height: 1.4;
}
.post-card-excerpt {
color: #4b5563;
flex: 1;
margin-bottom: 1rem;
}
.post-card-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: auto;
}
.post-card-tag {
padding: 0.25rem 0.75rem;
background: #f3f4f6;
border-radius: 9999px;
font-size: 0.75rem;
color: #374151;
}
Decision Framework: Grid vs Flexbox
Use this mental checklist:
Choose Flexbox When:
- Single dimension layout - Navigation bars, button groups, card contents
- Content-based sizing - Let items determine their own size
- Items of unknown or varying sizes - Tags, badges, dynamic lists
- Centering content - Quick vertical/horizontal centering
- Distributing space - Space-between, space-around, space-evenly
Choose Grid When:
- Two-dimensional layouts - Page layouts, dashboards
- Overlapping elements - Complex positioning scenarios
- Precise placement needed - Items must align in both directions
- Layout-first design - You know the structure ahead of content
- Complex responsive patterns - Different layouts at different breakpoints
Real-World Decision Examples
/* Scenario: Header with logo, nav, and actions */
/* Decision: FLEXBOX - Single row, content-driven spacing */
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Scenario: Product listing page */
/* Decision: GRID - Products align in rows AND columns */
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 2rem;
}
/* Scenario: Individual product card */
/* Decision: FLEXBOX - Single column with variable content */
.product-card {
display: flex;
flex-direction: column;
}
/* Scenario: Footer with multiple columns */
/* Decision: GRID - Need consistent column widths */
.footer {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: 4rem;
}
/* Scenario: Links within footer column */
/* Decision: FLEXBOX - Simple vertical list */
.footer-links {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
Common Patterns Compared
Equal Height Cards
/* With Flexbox - Cards stretch to match tallest sibling */
.cards-container-flex {
display: flex;
gap: 2rem;
}
.card-flex {
flex: 1;
display: flex;
flex-direction: column;
}
/* With Grid - Implicitly equal heights */
.cards-container-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
/* Both work - Grid is simpler for this case */
Auto-Filling Columns
/* Flexbox approach */
.flex-auto-columns {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.flex-auto-columns > * {
flex: 1 1 300px; /* Grow, shrink, base width */
}
/* Grid approach */
.grid-auto-columns {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
/* Grid gives more consistent results */
Sticky Footer
/* With Flexbox */
.page-flex {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-flex {
flex: 1;
}
/* With Grid */
.page-grid {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
/* Both work well - choose based on surrounding layout needs */
Responsive Design Strategies
Grid-First Responsive
/* Start with mobile single column, expand with breakpoints */
.responsive-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
@media (min-width: 640px) {
.responsive-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.responsive-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1280px) {
.responsive-grid {
grid-template-columns: repeat(4, 1fr);
}
}
Flexbox-First Responsive
/* Items wrap naturally, adjust with breakpoints */
.responsive-flex {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.responsive-flex > * {
flex: 1 1 100%; /* Full width on mobile */
}
@media (min-width: 640px) {
.responsive-flex > * {
flex: 1 1 calc(50% - 0.5rem);
}
}
@media (min-width: 1024px) {
.responsive-flex > * {
flex: 1 1 calc(33.333% - 0.67rem);
}
}
Performance Considerations
Both Grid and Flexbox are highly optimized in modern browsers. However:
- Avoid layout thrashing - Don't read layout properties immediately after changing styles
- Use
will-changesparingly - For animated grid/flex containers - Minimize nested layouts - Deep nesting can impact performance
- Use
containproperty - For isolated layout sections
/* Optimize complex layouts */
.optimized-grid {
display: grid;
contain: layout style;
}
/* For animated items */
.animated-card {
will-change: transform;
}
Conclusion
The Grid vs Flexbox debate isn't about which is better - it's about choosing the right tool for each situation. Master both, understand their strengths, and combine them for powerful, maintainable layouts.
My rule of thumb: start with Flexbox for components, reach for Grid when you need two-dimensional control.
For more frontend development tips, check out my guides on Tailwind CSS and React best practices. And explore my portfolio to see these layout techniques in action across real projects.
Keep building beautiful interfaces.