Understand the difference between CSS Flexbox and Grid with side-by-side examples. Learn when to use each layout system for responsive web design.
Every CSS developer eventually hits the same question: should I use Flexbox or Grid here? And every CSS developer eventually gets the same unhelpful answer: "it depends."
That answer is technically correct. It's also useless when you're staring at a design mockup and need to ship something by Friday. So let's replace vague advice with concrete rules, real code, and side-by-side comparisons that make the decision obvious.
Both Flexbox and Grid are CSS layout systems. Both are fully supported in every modern browser. Both handle responsive design well. But they were designed to solve fundamentally different layout problems, and choosing the wrong one doesn't just make your code messy — it makes things harder to maintain, harder to make responsive, and harder for the next developer to understand.
Here's how to know which one to reach for, every time.
Flexbox is a one-dimensional layout system. It handles either a row or a column at a time.
Grid is a two-dimensional layout system. It handles rows and columns simultaneously.
That single distinction drives every practical decision. If you only remember one thing from this article, remember this: Flexbox works along one axis. Grid works along two.
When you create a flex container, you choose a direction — row or column. Items flow in that single direction. If they wrap, each wrapped line is independent. The first row knows nothing about the second row. Items in line one don't align with items in line two unless they happen to have the same size.
.flex-container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.flex-item {
flex: 1 1 200px;
}This wraps items into multiple lines, but each line is laid out independently. If the first line has three items and the second has two, the two items on the second line will stretch to fill the space — they won't align under the first three.
When you create a grid container, you define both columns and rows. Every item is placed into that two-dimensional structure. Items in the second row align perfectly with items in the first row because they share the same column tracks.
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}If the last row only has two items, those two items sit in the first two columns. The third column stays empty. The alignment is locked to the grid structure.
A navigation bar is a horizontal list of links. It's inherently one-dimensional. Flexbox was literally built for this.
.navbar {
display: flex;
align-items: center;
gap: 1rem;
padding: 0 2rem;
height: 64px;
}
.navbar-logo {
margin-right: auto;
}
.navbar-link {
padding: 0.5rem 1rem;
text-decoration: none;
white-space: nowrap;
}The margin-right: auto on the logo pushes all the links to the right. Simple, readable, and exactly what Flexbox is designed for. Items distribute along a single row.
.navbar {
display: grid;
grid-template-columns: auto 1fr repeat(4, auto);
align-items: center;
gap: 1rem;
padding: 0 2rem;
height: 64px;
}This works, but you need to know how many links you have to define the columns. If you add a fifth link, you need to update the template. With Flexbox, you just add the element and it flows naturally.
Verdict: Flexbox wins. Navigation is one-dimensional content flow.
A card grid is a two-dimensional layout. You want cards to align both horizontally and vertically in a structured pattern. Grid handles this naturally.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.card {
background: var(--surface);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid var(--border);
}The auto-fill with minmax creates a responsive grid without a single media query. Cards are at least 300px wide. If the container is wide enough for four columns, you get four. If it shrinks, it drops to three, then two, then one. And every card in every row aligns perfectly with the cards above and below it.
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.card {
flex: 1 1 300px;
max-width: calc(33.333% - 1rem);
background: var(--surface);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid var(--border);
}This kind of works, but the last row is a problem. If you have seven cards in a three-column layout, the last card on the third row stretches to fill the entire remaining space. You need hacks — invisible spacer elements, specific max-width calculations, or complex flex-basis math — to make the last row behave.
With Grid, the last row just works. Seven cards in a three-column grid means the last row has one card sitting in the first column. No hacks needed.
Verdict: Grid wins. Card layouts need two-dimensional alignment.
A full page layout with named regions is the textbook Grid use case.
.page-layout {
display: grid;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 280px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }
@media (max-width: 768px) {
.page-layout {
grid-template-areas:
"header"
"content"
"sidebar"
"footer";
grid-template-columns: 1fr;
}
}Named grid areas make the layout self-documenting. You can literally read the structure by looking at the grid-template-areas property. And reorganizing the layout for mobile is just rearranging the strings — no DOM changes, no flexbox ordering tricks.
.page-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-area {
display: flex;
flex: 1;
}
.sidebar {
width: 280px;
flex-shrink: 0;
}
.content {
flex: 1;
}
@media (max-width: 768px) {
.main-area {
flex-direction: column;
}
.sidebar {
width: 100%;
order: 2;
}
.content {
order: 1;
}
}You need nested flex containers. You need explicit order properties to reorder content on mobile. You need to manage widths manually. It works, but it's fragile and harder to reason about than the Grid version.
Verdict: Grid wins decisively. Page layouts are inherently two-dimensional.
This is the simplest comparison and the one that surprises people.
.center-grid {
display: grid;
place-items: center;
min-height: 100vh;
}Three lines. place-items: center is shorthand for align-items: center and justify-items: center. Done.
.center-flex {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}Four lines. Both work perfectly. Grid is slightly shorter, but the difference is cosmetic. Use whichever feels more natural in context.
Verdict: Tie. Both are clean solutions.
Forms with labels and inputs in a structured two-column pattern are a strong Grid use case.
.form-grid {
display: grid;
grid-template-columns: 180px 1fr;
gap: 1rem 1.5rem;
align-items: center;
}
.form-grid label {
text-align: right;
font-weight: 600;
}
.form-grid .full-width {
grid-column: 1 / -1;
}Labels and inputs align perfectly. Every label is the same width. Every input stretches to fill the remaining space. Adding a full-width element (like a textarea or a submit button) is one line: grid-column: 1 / -1.
.form-row {
display: flex;
align-items: center;
gap: 1.5rem;
margin-bottom: 1rem;
}
.form-row label {
width: 180px;
flex-shrink: 0;
text-align: right;
font-weight: 600;
}
.form-row input,
.form-row select,
.form-row textarea {
flex: 1;
}You need a wrapping element for each row. Each row is an independent flex container. If one label is longer than 180px, it will either overflow or you need to adjust the width — and that adjustment affects only that row unless you manually keep all rows in sync.
Verdict: Grid wins. Form layouts benefit from consistent column alignment across rows.
A list of tags, badges, or chips that wraps naturally based on available space.
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
background: var(--tag-bg);
font-size: 0.875rem;
white-space: nowrap;
}Tags have different widths based on their text content. They flow naturally, wrapping when they run out of space. Each tag takes only as much width as it needs.
.tag-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, auto));
gap: 0.5rem;
}Grid forces items into column tracks. Short tags get stretched to match the column width. Long tags might overflow. You lose the natural, content-sized flow that makes tag lists look good.
Verdict: Flexbox wins. Tags are variable-width content flowing in one dimension.
A dashboard where some widgets span two columns, some span two rows, and some are standard 1x1.
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(200px, auto);
gap: 1.5rem;
}
.widget-large {
grid-column: span 2;
grid-row: span 2;
}
.widget-wide {
grid-column: span 2;
}
.widget-tall {
grid-row: span 2;
}
@media (max-width: 1024px) {
.dashboard {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.dashboard {
grid-template-columns: 1fr;
}
.widget-large,
.widget-wide {
grid-column: span 1;
}
.widget-tall {
grid-row: span 1;
}
}Widgets span multiple rows and columns with simple, declarative properties. The responsive breakpoints collapse the grid naturally. Grid handles the placement algorithm automatically — if a widget doesn't fit in the current position, Grid moves to the next available spot.
Trying to replicate this with Flexbox would require absolute positioning, manual width calculations, and a substantial amount of JavaScript. Don't do it.
Verdict: Grid wins overwhelmingly. Multi-span layouts are Grid's strongest use case.
The best layouts often combine both. Grid handles the macro layout structure. Flexbox handles the micro component alignment within grid cells.
/* Grid for the overall page structure */
.app-layout {
display: grid;
grid-template-columns: 260px 1fr 300px;
grid-template-rows: 64px 1fr;
min-height: 100vh;
}
/* Flexbox for the header content alignment */
.app-header {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.5rem;
}
/* Flexbox for the sidebar navigation */
.app-sidebar {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 1rem;
}
/* Grid for the content area card layout */
.content-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
padding: 1.5rem;
}
/* Flexbox for content inside each card */
.card-content {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}This pattern — Grid for structure, Flexbox for alignment — covers roughly 90% of real-world layout needs. The page shell is Grid. The header is Flexbox. The card grid is Grid. The card internals are Flexbox. Each tool does what it's best at.
Both Grid and Flexbox can create responsive layouts without media queries, but they do it differently.
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}auto-fit collapses empty tracks so items stretch to fill the row. auto-fill keeps empty tracks so items maintain their maximum size. Choose based on whether you want items to stretch or stay fixed when there's extra space.
.responsive-flex {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.responsive-flex > * {
flex: 1 1 250px;
}Items grow and shrink around a 250px basis. But remember: the last row will stretch unevenly if it doesn't have enough items to fill it. For content where equal sizing matters, Grid's auto-fill pattern is better.
.sidebar-layout {
display: grid;
grid-template-columns: min(280px, 100%) 1fr;
}
@media (max-width: 768px) {
.sidebar-layout {
grid-template-columns: 1fr;
}
}.sidebar-layout {
display: flex;
flex-wrap: wrap;
}
.sidebar {
flex: 1 1 280px;
}
.main-content {
flex: 999 1 500px;
}The enormous flex-grow: 999 on the main content means it claims almost all available space. When the container is too narrow for both items to sit side by side (below 780px, roughly), they wrap. The sidebar drops below. No media query needed.
This is a clever technique, but it's harder to reason about than Grid's explicit approach. Use it when you want media-query-free responsiveness and don't need precise control over the breakpoint.
Both Flexbox and Grid are fast. Browser rendering engines have optimized both for years. In practice, you'll never see a performance difference between them for typical layouts.
That said, some things to keep in mind:
Grid's subgrid feature, available since 2023 in all major browsers, lets nested grids inherit their parent's track definitions. This avoids duplicating track sizes and ensures alignment across nested components:
.parent-grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.child-spanning-all {
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid;
}The child's three columns match the parent's three columns exactly. Without subgrid, you'd need to manually duplicate the column definition or use fixed values. This is particularly useful for table-like layouts, form groups, and any component where nested elements need to align with the outer grid.
Flexbox calculations are generally simpler for the browser because it only handles one axis at a time. For lists with hundreds of items, Flexbox may trigger fewer layout recalculations. But this only matters at extreme scale — think virtualized lists with thousands of dynamically sized items. For normal applications, pick based on correctness and readability, not performance.
If you have a list of items where each item's size is entirely determined by its content and you want them to flow naturally, Flexbox is the right choice. Grid will force items into tracks, which may not be what you want.
A three-column layout where the center column is wider than the sides is trivial with Grid:
.three-col {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
gap: 2rem;
}With Flexbox, you need to set explicit widths or flex ratios on each child, and the behavior when content overflows is less predictable.
Flex items have flex-shrink: 1 by default. This means they'll compress below their natural size if the container is too small. This catches people off guard constantly:
/* The image will shrink below 200px if the container is narrow */
.card {
display: flex;
gap: 1rem;
}
.card-image {
width: 200px;
/* Fix: add flex-shrink: 0 to prevent compression */
flex-shrink: 0;
}You don't need grid-template-areas for every Grid layout. For simple equal-column grids, repeat() is cleaner:
/* Don't do this for a simple equal grid */
.grid {
display: grid;
grid-template-areas: "a b c" "d e f";
}
/* Do this instead */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}Reserve grid-template-areas for layouts where the visual structure is complex enough that named areas improve readability — like page shells with headers, sidebars, and footers.
Use this to make the call quickly:
Use Flexbox when:
Use Grid when:
Use both when:
If you're experimenting with CSS Grid layouts, visual generators can accelerate your workflow significantly. The CSS Grid Generator on akousa.net lets you define rows, columns, and gaps visually and export the CSS. Similarly, the CSS Animation Generator is useful when you want to add transitions to your layout changes. For cleaning up your production stylesheets, the CSS Minifier handles whitespace and redundancy removal, and the CSS Unit Converter helps when you need to switch between px, rem, em, and other units for responsive designs.
These tools work directly in the browser with no signup or installation required — useful for quick prototyping sessions or teaching CSS layout concepts.
Flexbox and Grid are not competitors. They're collaborators. The "which should I use" question has a straightforward answer once you internalize the one-dimensional vs two-dimensional distinction.
Flexbox is for distributing space along a single axis. Navigation bars, toolbars, tag lists, card internals, anything that flows in one direction.
Grid is for placing items in a two-dimensional structure. Page layouts, card grids, dashboards, forms, anything where alignment across both rows and columns matters.
The best modern CSS uses both. Grid for the structure. Flexbox for the details. Learn both well, and you'll find yourself writing less CSS that does more — and that CSS will be easier to read, easier to maintain, and easier to make responsive.
Stop debating which one is "better." Start using the right one for each job.