The Fundamental Difference
The single most important thing to understand about CSS Grid and Flexbox is the axis model:
- Flexbox is one-dimensional. It lays out items along a single axis — either a row or a column. Items can wrap to the next line, but each line is independent.
- CSS Grid is two-dimensional. It controls both rows and columns simultaneously. You define a grid structure, and items are placed within that structure.
This is not a matter of one being "better" than the other. They solve different problems, and the best layouts use both. Think of it this way: Grid defines the structure, Flexbox aligns the content.
Flexbox (1D)
Grid (2D)
In the Flexbox example, items distribute space along a single row. In the Grid example, items are placed in both rows and columns, and can span across cells. Both are powerful. The key is knowing which to reach for.
When to Use Flexbox
Flexbox excels when you need to distribute space among items along a single axis. Here are the most common use cases:
Navigation Bars
A navigation bar is a row of items that need to be spaced and aligned. Flexbox handles this perfectly.
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 64px;
}
.navbar-logo {
font-weight: 700;
font-size: 1.25rem;
}
.navbar-links {
display: flex;
gap: 24px;
list-style: none;
}
.navbar-actions {
display: flex;
gap: 12px;
align-items: center;
}
This creates a nav bar with the logo on the left, links in the center, and action buttons on the right. The space-between pushes the three groups apart, and each group internally uses flex with gap to space its children.
Button Groups and Toolbars
.button-group {
display: flex;
gap: 8px;
align-items: center;
}
/* Make one button fill remaining space */
.button-group .primary {
flex: 1;
}
/* Vertical toolbar */
.toolbar {
display: flex;
flex-direction: column;
gap: 4px;
width: 48px;
}
Centering Content
The classic "center both horizontally and vertically" pattern is a Flexbox one-liner:
.centered {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
Card Content Alignment
Inside a card, Flexbox ensures the footer sticks to the bottom regardless of content height:
.card {
display: flex;
flex-direction: column;
height: 100%;
}
.card-body {
flex: 1; /* Takes up all available space */
}
.card-footer {
margin-top: auto; /* Pushes to the bottom */
}
If you are arranging items in a line (horizontal or vertical) and the relationship between items is about distributing space, reach for Flexbox first.
When to Use CSS Grid
CSS Grid shines when you need to control placement in two dimensions or when you want the layout to be defined by the container rather than the content.
Page Layouts
The classic header/sidebar/main/footer page layout is a Grid pattern:
.page-layout {
display: grid;
grid-template-columns: 280px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* Collapse sidebar on mobile */
@media (max-width: 768px) {
.page-layout {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"footer";
}
.sidebar { display: none; }
}
The grid-template-areas property makes the layout readable at a glance. You can see the structure just by reading the CSS. Try building this with Flexbox and you will appreciate why Grid exists.
Card Grids
Responsive card grids are where Grid truly outshines Flexbox. The auto-fill and minmax combination creates a responsive grid without any media queries:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
/* Cards automatically reflow:
- 4 columns on wide screens
- 3 columns on medium screens
- 2 columns on tablets
- 1 column on mobile
All without a single media query. */
Need to generate this CSS visually? Use NexTool's CSS Grid Generator to build grid layouts with a point-and-click interface and export production-ready code.
Dashboard Layouts
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: auto;
gap: 16px;
}
/* A widget that spans 2 columns */
.widget-wide {
grid-column: span 2;
}
/* A widget that spans 2 rows */
.widget-tall {
grid-row: span 2;
}
/* Featured widget: 2x2 */
.widget-featured {
grid-column: span 2;
grid-row: span 2;
}
Form Layouts
Forms with labels and inputs aligned in columns are a Grid use case:
.form-grid {
display: grid;
grid-template-columns: 140px 1fr;
gap: 12px 16px;
align-items: center;
}
/* Full-width elements (like submit buttons) */
.form-grid .full-width {
grid-column: 1 / -1;
}
/* Two inputs side by side */
.form-row {
grid-column: 2;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
Side-by-Side Comparisons
Let us compare how the same layout is built with each approach. This makes the differences tangible.
Comparison 1: Equal-Height Columns
Flexbox
.columns {
display: flex;
gap: 24px;
}
.column {
flex: 1;
/* Height automatically
matches tallest sibling */
}
Grid
.columns {
display: grid;
grid-template-columns:
repeat(3, 1fr);
gap: 24px;
/* Equal height by default */
}
Verdict: Both work equally well here. Flexbox is slightly more concise but less explicit about column count. Grid forces you to declare the structure upfront, which can be clearer.
Comparison 2: Responsive Wrapping
Flexbox
.items {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.item {
flex: 1 1 300px;
/* Grows, shrinks,
min 300px base */
}
/* Items may have uneven
widths on the last row */
Grid
.items {
display: grid;
grid-template-columns:
repeat(auto-fill,
minmax(300px, 1fr));
gap: 16px;
}
/* Items always have
equal widths
on every row */
Verdict: Grid wins here. With Flexbox, items on the last row might stretch to fill space unevenly (e.g., two items filling a three-column width). Grid's auto-fill + minmax produces consistently even columns on every row.
Comparison 3: Overlapping Elements
Flexbox
/* Not possible with Flexbox.
You need position: absolute
or negative margins,
which breaks the flex
flow and is fragile. */
Grid
.stack {
display: grid;
}
.stack > * {
grid-area: 1 / 1;
/* All children occupy
the same cell,
stacking naturally */
}
.overlay {
align-self: end;
padding: 16px;
background: rgba(0,0,0,0.7);
}
Verdict: Grid wins decisively. Overlapping elements (like image overlays, stacked cards, or hero sections with text on top of an image) are natural with Grid and hacky with Flexbox.
Comparison 4: Item Reordering
Flexbox
.container {
display: flex;
}
.item:nth-child(1) {
order: 2;
}
.item:nth-child(2) {
order: 1;
}
/* Can reorder within
the flex flow */
Grid
.container {
display: grid;
grid-template-columns: 1fr 1fr;
}
.item:nth-child(1) {
grid-column: 2;
grid-row: 1;
}
.item:nth-child(2) {
grid-column: 1;
grid-row: 1;
}
/* Can place anywhere */
Verdict: Flexbox's order property is simpler for one-dimensional reordering. Grid allows placing items anywhere on the grid, which is more powerful but more verbose for simple cases.
Using Grid and Flexbox Together
The real power comes from combining both. Here is a production pattern that most modern websites use:
<div class="page">
<header class="header">
<nav class="navbar">...</nav>
</header>
<main class="content">
<div class="card-grid">
<article class="card">...</article>
<article class="card">...</article>
<article class="card">...</article>
</div>
</main>
<footer class="footer">...</footer>
</div>
/* GRID: Page structure */
.page {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
/* FLEXBOX: Navbar items */
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 64px;
}
/* GRID: Card layout */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
padding: 24px;
}
/* FLEXBOX: Card internals */
.card {
display: flex;
flex-direction: column;
background: #111118;
border-radius: 12px;
overflow: hidden;
}
.card-body {
flex: 1;
padding: 20px;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
border-top: 1px solid #1e1e2e;
}
/* FLEXBOX: Footer links */
.footer {
display: flex;
justify-content: center;
gap: 24px;
padding: 32px;
}
Notice the pattern: Grid handles the two-dimensional structure (page layout, card grid), while Flexbox handles one-dimensional alignment (navbar, card content, footer links). This is the mental model that will serve you for every layout you build.
Real-World Layout Patterns
Pattern 1: Holy Grail Layout
The classic header, left sidebar, main content, right sidebar, and footer layout:
.holy-grail {
display: grid;
grid-template:
"header header header" auto
"left main right" 1fr
"footer footer footer" auto
/ 200px 1fr 200px;
min-height: 100vh;
gap: 0;
}
.header { grid-area: header; }
.left { grid-area: left; }
.main { grid-area: main; }
.right { grid-area: right; }
.footer { grid-area: footer; }
@media (max-width: 768px) {
.holy-grail {
grid-template:
"header" auto
"main" 1fr
"footer" auto
/ 1fr;
}
.left, .right { display: none; }
}
Pattern 2: Masonry-Like Layout
While true CSS masonry is still behind a flag in most browsers as of early 2026, you can approximate it with Grid:
/* Approximate masonry with column-count (works today) */
.masonry {
column-count: 3;
column-gap: 16px;
}
.masonry-item {
break-inside: avoid;
margin-bottom: 16px;
}
@media (max-width: 768px) {
.masonry { column-count: 2; }
}
@media (max-width: 480px) {
.masonry { column-count: 1; }
}
/* True CSS Masonry (when available) */
@supports (grid-template-rows: masonry) {
.masonry {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: masonry;
gap: 16px;
column-count: initial; /* Override fallback */
}
.masonry-item {
break-inside: initial;
margin-bottom: 0;
}
}
Pattern 3: Responsive Feature Section
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 32px;
padding: 64px 24px;
}
.feature {
display: flex;
flex-direction: column;
gap: 12px;
}
.feature-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(99, 102, 241, 0.1);
border-radius: 12px;
font-size: 1.5rem;
}
.feature h3 {
font-size: 1.125rem;
font-weight: 600;
}
.feature p {
color: #94a3b8;
line-height: 1.6;
}
Pattern 4: Pricing Table
.pricing {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
align-items: start;
max-width: 1000px;
margin: 0 auto;
}
.pricing-card {
display: flex;
flex-direction: column;
border: 1px solid #1e1e2e;
border-radius: 16px;
padding: 32px;
}
.pricing-card.featured {
border-color: #6366f1;
position: relative;
}
.pricing-card .price {
font-size: 3rem;
font-weight: 800;
margin: 16px 0;
}
.pricing-card .features-list {
flex: 1;
list-style: none;
padding: 0;
margin: 24px 0;
}
.pricing-card .features-list li {
padding: 8px 0;
display: flex;
align-items: center;
gap: 8px;
}
.pricing-card .cta {
margin-top: auto; /* Pushed to bottom by flex */
width: 100%;
}
CSS Subgrid: Aligning Nested Elements
Subgrid solves one of the most frustrating layout problems: aligning content across cards when content lengths vary. Without subgrid, card titles, descriptions, and buttons misalign when content has different lengths. With subgrid, nested elements participate in the parent grid's tracks.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4; /* Card occupies 4 parent rows: image, title, desc, button */
gap: 0;
border: 1px solid #1e1e2e;
border-radius: 12px;
overflow: hidden;
}
/* Now, across all cards in a row:
- All images are the same height
- All titles align on the same baseline
- All descriptions start at the same position
- All buttons sit at the same level */
.card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.card h3 {
padding: 16px 16px 0;
align-self: start;
}
.card p {
padding: 8px 16px;
color: #94a3b8;
}
.card .btn {
margin: 16px;
align-self: end;
}
CSS subgrid has full support in Chrome, Firefox, Safari, and Edge since 2024. You can use it in production today without fallbacks for modern audiences.
Subgrid for Form Alignment
Subgrid is also perfect for aligning form fields in a grid, where each field group has a label and input that should align with neighboring groups:
.form {
display: grid;
grid-template-columns: auto 1fr;
gap: 12px 16px;
}
.field-group {
display: grid;
grid-column: 1 / -1;
grid-template-columns: subgrid;
align-items: center;
}
/* All labels share the same column width,
determined by the widest label.
All inputs share the remaining space. */
Container Queries: The Missing Piece
Container queries are the most significant CSS feature to reach full browser support in recent years. They allow you to style elements based on the size of their parent container instead of the viewport.
Why Container Queries Matter
With media queries, a card component always responds to the viewport width. But what if that card appears in a narrow sidebar on desktop? Media queries see a wide viewport and render the desktop layout — even though the card only has 280px of space. Container queries solve this.
/* Define a containment context */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Style based on container width */
.card {
display: flex;
flex-direction: column;
gap: 12px;
}
/* When the card's container is at least 500px wide,
switch to horizontal layout */
@container card (min-width: 500px) {
.card {
flex-direction: row;
align-items: center;
}
.card-image {
width: 200px;
flex-shrink: 0;
}
.card-body {
flex: 1;
}
}
/* When the card's container is narrow (sidebar),
show a compact version */
@container card (max-width: 300px) {
.card-image {
display: none;
}
.card-body h3 {
font-size: 0.9rem;
}
}
Container Query Units
Container queries also introduce new units that are relative to the container's size:
.card-container {
container-type: inline-size;
}
.card h3 {
/* Font size scales with container width */
font-size: clamp(1rem, 3cqi, 1.5rem);
/* cqi = 1% of container inline size */
}
.card p {
font-size: clamp(0.8rem, 2cqi, 1rem);
}
/* Available container units:
cqw — container query width (1%)
cqh — container query height (1%)
cqi — container query inline size (1%)
cqb — container query block size (1%)
cqmin — smaller of cqi/cqb
cqmax — larger of cqi/cqb */
Modern CSS Techniques (2026)
Logical Properties
Logical properties make your layouts work correctly in all writing directions (LTR, RTL) without separate stylesheets:
/* Instead of: */
.box {
margin-left: 16px;
margin-right: 16px;
padding-top: 12px;
padding-bottom: 12px;
border-left: 3px solid #6366f1;
}
/* Use logical properties: */
.box {
margin-inline: 16px; /* Start and end (left/right in LTR) */
padding-block: 12px; /* Start and end (top/bottom in LTR) */
border-inline-start: 3px solid #6366f1; /* Left in LTR, right in RTL */
}
The :has() Selector
The :has() selector (the "parent selector") enables layout changes based on child content:
/* Card layout changes based on whether it has an image */
.card:has(img) {
grid-template-columns: 200px 1fr;
}
.card:not(:has(img)) {
grid-template-columns: 1fr;
}
/* Form field styling based on input state */
.field:has(input:invalid) {
border-color: #ef4444;
}
.field:has(input:focus) {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
}
/* Navbar changes when it contains a search input */
.navbar:has(.search-input:focus) {
background: rgba(0, 0, 0, 0.95);
}
Scroll-Driven Animations
A newer CSS feature that eliminates the need for JavaScript scroll event handlers:
.fade-in {
animation: fadeIn linear;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Progress bar that tracks scroll position */
.progress-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: #6366f1;
transform-origin: left;
animation: scaleProgress linear;
animation-timeline: scroll();
}
@keyframes scaleProgress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Performance Considerations
Both Grid and Flexbox are highly optimized in modern browsers, but there are a few things to keep in mind:
Layout Recalculation
- Flexbox can trigger multiple layout passes when items have complex flex values and wrapping is involved. The browser may need to calculate item sizes, discover they wrap, then recalculate on each line.
- Grid calculates all tracks upfront and places items in a single pass. For complex layouts with many items, Grid can be faster because the algorithm is more deterministic.
Content-Based vs. Container-Based Sizing
- Flexbox is content-based: item sizes influence the layout. This means adding or changing content can trigger re-layout of siblings.
- Grid is container-based: track sizes are defined by the grid container. Items fill their cells without affecting sibling positions. This leads to more predictable performance.
Practical Impact
For most applications, the performance difference is negligible. You will only notice it with thousands of elements (e.g., large data tables or infinite-scroll feeds). In those cases, prefer Grid for the outer structure and minimize nested flex containers.
Avoid deeply nested flex containers (flex inside flex inside flex). Each level adds a layout calculation pass. Flatten your structure where possible, or use Grid to skip a level of nesting.
The Decision Framework
When you sit down to build a layout, use this decision framework:
| Question | If Yes |
|---|---|
| Is the layout a single row or column of items? | Flexbox |
| Do items need to align in both rows AND columns? | Grid |
| Should the content determine the layout (intrinsic)? | Flexbox |
| Should the container determine the layout (extrinsic)? | Grid |
| Do elements need to overlap? | Grid |
| Is it a navigation bar or toolbar? | Flexbox |
| Is it a card grid or dashboard? | Grid |
| Is it alignment within a component? | Flexbox |
| Is it the overall page structure? | Grid |
When in doubt, start with Grid for the outer layout and Flexbox for the inner components. You can always refactor later — switching between Grid and Flexbox usually requires changing only 2-3 lines of CSS.
Build CSS Layouts Visually
Use NexTool's free visual tools to generate Grid and Flexbox CSS without memorizing every property.
CSS Grid Generator Flexbox PlaygroundCSS Layout Tools
Visual tools accelerate your workflow when prototyping layouts. Here are NexTool's free CSS tools:
Frequently Asked Questions
Use Flexbox for one-dimensional layouts (a row OR a column) where items need to distribute space along a single axis. Use CSS Grid for two-dimensional layouts (rows AND columns) where you need precise control over placement in both directions. In practice, most modern layouts use both: Grid for the overall page structure and Flexbox for component-level alignment within grid cells.
Absolutely, and you should. CSS Grid and Flexbox are complementary, not competing technologies. A common pattern is to use Grid for the macro layout (page sections, card grids, dashboard panels) and Flexbox for the micro layout (navigation items, button groups, card content alignment). Grid cells can contain flex containers, and vice versa.
CSS subgrid allows a grid item's children to participate in the parent grid's track sizing. This means nested elements can align to the same grid lines as their parent's siblings. Use subgrid when you need consistent alignment across card layouts — for example, aligning titles, descriptions, and buttons across a row of cards regardless of content length. Subgrid has full browser support as of 2024.
Container queries let you style elements based on the size of their parent container, not the viewport. While media queries respond to the browser window width (useful for page-level layout), container queries respond to the component's available space (useful for reusable components). This means a card component can adapt its layout whether it appears in a narrow sidebar or a wide main content area, without any JavaScript.