CSS Grid vs Flexbox: When to Use Each (With Real Examples)
Both lay out elements without floats or absolute positioning, and both are genuinely production-ready today — the real question isn't which is 'better,' it's whether your layout is fundamentally one-dimensional or two.
Flexbox and CSS Grid both arrived to solve the same old problem — laying out elements on a page without reaching for floats, table hacks or absolute-position workarounds — and both are fully production-ready in every modern browser today, so "which one is more supported" hasn't been the real question for years. The actual distinction is about dimensionality: Flexbox lays things out along a single axis at a time (a row, or a column), while Grid lays things out along two axes simultaneously (rows and columns together, as one coordinated system). Once that distinction clicks, most "which should I use" hesitation resolves itself, because the two aren't really competing for the same job.
The core distinction: one axis vs two
Flexbox is fundamentally one-dimensional — you pick a direction (row or column) with flex-direction, and everything inside lays out along that single axis, with the cross-axis mostly handling alignment and wrapping rather than precise positioning. Grid is fundamentally two-dimensional — you define both rows and columns up front as a single coordinated structure, and every item can be placed at a specific row-and-column intersection, or span multiple of either, independent of source order. This is the one sentence that actually settles most Grid-vs-Flexbox debates: if you're arranging things in a line (even a wrapping line), that's Flexbox's job; if you're arranging things in a genuine grid — rows and columns that need to line up with each other — that's Grid's job.
Sponsored
Where Flexbox is the obviously right tool
- A navigation bar — logo on the left, links in the middle, a button on the right, all in one row with space distributed between them.
- A row of buttons or form controls that need equal spacing or to be centered together.
- A card's internal layout — an icon, a title and a description stacked or arranged in a single flowing direction inside the card.
- Centering a single item both horizontally and vertically inside its container — Flexbox's align-items and justify-content combination handles this in two lines.
- Any list of items that should wrap onto new lines as needed, where each wrapped line doesn't need to line up in columns with the line above it.
Where Grid is the obviously right tool
- A page's overall layout — header, sidebar, main content and footer, all defined as named grid areas in one structure.
- A photo gallery or product grid where every row and column genuinely needs to align — Grid guarantees that alignment; Flexbox wrapping does not.
- A dashboard with cards of varying sizes that need to span multiple rows or columns — a card that's 2 columns wide and 2 rows tall sitting alongside single-cell cards.
- Any layout where you want to define the structure once (the grid) and place items into specific cells, rather than relying on source order and wrapping behavior to produce the arrangement.
- Overlapping elements at specific grid positions — Grid supports placing multiple items at overlapping row/column coordinates directly; doing the equivalent in Flexbox generally needs extra positioning tricks.
Side-by-side comparison
- Flexbox — one-dimensional (row or column at a time). Alignment: strong along the main axis, good but less precise on the cross-axis. Wrapping: items wrap based on available space, but wrapped rows don't align to each other as columns. Best for: content-driven layouts where sizes are somewhat flexible and dictated by the content itself.
- Grid — two-dimensional (rows and columns together). Alignment: precise on both axes simultaneously, by design. Wrapping: not really 'wrapping' in the same sense — you define the structure and items are placed into it, including explicit spans. Best for: layout-driven structures where you want to define the container's shape first and place content into it.
- Combining them — using Grid for a page's overall macro-layout and Flexbox inside individual grid cells for their internal one-dimensional arrangement is an extremely common, entirely valid pattern, not an either/or choice.
A concrete example: a product card grid
Say you're building a grid of product cards — a classic e-commerce layout. The outer arrangement (how many cards per row, consistent gutters, rows that align across the whole grid even as the number of cards varies) is a Grid job: `display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1.5rem;` gives you a responsive grid that reflows its column count automatically as the viewport changes, with every row staying aligned. Inside each individual card, though, the internal layout — an image on top, a title, a price and an "Add to Cart" button stacked vertically, with the button pushed to the bottom regardless of how much text is above it — is a one-dimensional, Flexbox job: `display: flex; flex-direction: column;` on the card, with `margin-top: auto` on the button to push it down. Neither tool alone produces this cleanly; the combination is what does.
A concrete example: a navigation bar
A typical site header — logo on the left, a set of nav links in the middle or right, maybe a call-to-action button at the far right — is a textbook single-axis layout, and Flexbox handles it directly: `display: flex; justify-content: space-between; align-items: center;` on the header puts the logo and nav group at opposite ends and vertically centers everything, with no need for a two-dimensional grid structure at all. Reaching for Grid here isn't wrong exactly, but it's more setup than the problem actually calls for — a genuine sign that the layout is one-dimensional and Flexbox is the more direct tool.
Responsive behavior: how each one adapts
Flexbox's flex-wrap lets items reflow onto new lines as space runs out, and flex-grow/flex-shrink let individual items expand or contract to fill available space — this is great for content that should adapt somewhat fluidly, but the tradeoff is that wrapped rows don't align as columns with each other, since Flexbox has no concept of a shared column structure across wrapped lines. Grid's repeat(auto-fit, ...) or repeat(auto-fill, ...) with minmax() achieves a comparable responsive reflow — the number of columns adjusts automatically to the container's width — but crucially keeps every row's columns aligned with every other row's, because the column structure is defined once for the whole grid rather than emerging from individual items wrapping independently. For a layout where visual alignment across rows genuinely matters (a gallery, a dashboard), this is a meaningful practical difference, not just a theoretical one.
Browser support and whether that's still a real consideration
Both Flexbox and CSS Grid have had full support across all major modern browsers for years at this point, so browser compatibility genuinely isn't a practical reason to avoid either one in a new project today — this used to be a real consideration in Grid's early days and simply isn't anymore. If you're maintaining code that specifically needs to support very old browser versions, that's a project-specific constraint worth checking directly rather than assuming from general modern web development practice, but for the overwhelming majority of new work, both tools are equally safe choices from a support standpoint, and the decision should be driven entirely by which one matches the layout's actual shape.
Named grid areas: Grid's most underused feature
grid-template-areas lets you name regions of a layout directly and place items into them by name, which produces some of the most genuinely readable CSS layout code available — you can look at the grid-template-areas declaration and see the actual shape of the page, rather than reverse-engineering it from a series of column and row numbers. A typical page shell — `grid-template-areas: "header header" "sidebar content" "footer footer";` alongside matching column and row size definitions — reads almost like an ASCII diagram of the layout itself, and each child element is placed with a simple `grid-area: header;` (or sidebar, content, footer) rather than needing to track numeric row and column positions. This is one of the clearest places where Grid does something Flexbox structurally cannot: Flexbox has no equivalent concept of named, addressable regions, because it doesn't define a coordinated two-dimensional structure to name regions within in the first place.
Alignment properties: same names, subtly different behavior
Both Flexbox and Grid share property names like justify-content, align-items and align-content, which helps with learning one after the other, but it's worth knowing they don't always express identical concepts across the two contexts. In Flexbox, justify-content works along the main axis (whichever direction flex-direction points) and align-items works along the cross-axis — so which physical direction each property actually affects depends on your flex-direction setting. In Grid, justify-content and align-content operate along the two fixed axes (inline/row and block/column) more consistently regardless of any direction setting, since Grid doesn't have a single "main axis" the way Flexbox does — it has two axes operating simultaneously. Grid additionally offers justify-items and align-items for positioning content within individual grid cells, which don't have a direct Flexbox equivalent in the same form, since Flexbox items don't sit inside distinct addressable cells the way Grid items do. Testing a specific alignment combination directly rather than assuming it transfers identically from Flexbox knowledge to Grid (or vice versa) is worth doing the first few times you switch contexts, until the distinction becomes automatic.
Common mistakes
- Reaching for Grid on a genuinely one-dimensional layout (a nav bar, a row of buttons) — it works, but it's more structure than the problem needs, and Flexbox expresses the same intent more directly.
- Fighting Flexbox's wrap behavior to force row-to-row column alignment across multiple wrapped lines — this is exactly the two-dimensional alignment problem Grid solves natively, and trying to hack it out of Flexbox usually ends up more fragile than just switching.
- Treating the choice as page-wide and all-or-nothing, instead of using Grid for the outer structure and Flexbox for internal, one-dimensional sections — the two combine constantly in real layouts.
- Not using minmax() with Grid's auto-fit/auto-fill for responsive column counts, and instead hardcoding a fixed number of columns that doesn't adapt to the viewport.
- Forgetting that Grid items can be explicitly placed and spanned (grid-column, grid-row) independent of their order in the HTML — a layout that only ever relies on Grid's implicit auto-placement is leaving a lot of Grid's actual power unused.
A quick way to decide
Ask whether the layout you're building is fundamentally a line — even a wrapping one — or fundamentally a grid where rows and columns both need to align and be addressable. A single row or column of items, a nav bar, centering one thing inside another, content stacked in one flowing direction: Flexbox. A page's overall structure, a gallery or card grid that needs row/column alignment, anything with items that span multiple rows or columns: Grid. And for anything more than a single, simple section, expect to use both — Grid for the outer shape, Flexbox inside individual pieces of it — rather than picking one tool for the entire page.
The gap property: one thing that works identically in both
It's worth calling out one place the two tools behave identically, since it removes a source of hesitation: the gap property (and its row-gap/column-gap variants) works the same way in both Flexbox and Grid, adding consistent spacing between items without needing margin hacks on individual children or the older "margin on all but the last child" workaround that used to be standard practice before gap had wide support. Before gap was reliably supported, spacing between flex items typically meant applying a margin to every item and then subtracting or overriding it on the first or last item to avoid uneven outer edges — a fragile pattern that broke easily when items were reordered or conditionally rendered. gap sidesteps all of that in both contexts, applying spacing purely between items rather than around them, which is part of why there's genuinely no reason to still reach for the old margin-based spacing tricks in a modern codebase using either Flexbox or Grid.
The short version: Flexbox is one-dimensional and excels at rows or columns of content that should flow and align along a single axis; Grid is two-dimensional and excels at structures where rows and columns both need to be defined and align together. They're not competing for the same job, and the strongest real-world layouts typically use Grid for the page's macro-structure and Flexbox for the one-dimensional arrangement inside individual sections or cards.
Tools used in this article
Sponsored
Frequently asked questions
Flexbox is generally the gentler starting point since it solves simpler, single-axis problems you'll hit immediately (centering, a row of buttons). Grid becomes valuable once you're building more structured, two-dimensional layouts like a page shell or a gallery.
TechToolsCenter Team
Product & Tools
The team behind TechToolsCenter — building fast, private, browser-based tools and writing practical guides on how to get the most out of them.
Related articles
CSS Container Queries Explained: What They Are and When to Use Them
Media queries respond to the whole viewport; container queries respond to the space a component actually has — which is what "responsive" should have meant for reusable components all along.
Box Shadow vs Drop Shadow in CSS: What Actually Differs
Both add a shadow, both accept nearly identical values, and yet they behave completely differently the moment your element isn't a plain rectangle.
SSR vs SSG vs CSR: Server-Side Rendering, Static Site Generation and Client-Side Rendering Explained
The same three letters keep showing up in every modern framework's docs — the real question each one answers is simply: at what point does the HTML a browser receives actually get built?