You’re building a dashboard: a header, a sidebar, a main content area, a footer. You reach for Flexbox because that’s the layout tool you know, and twenty minutes later you’ve got three nested display: flex containers, a sidebar that won’t hold its width without a magic number, and a footer that floats halfway up the page when the content is short. None of that is a Flexbox bug. You’re using a one-dimensional tool on a two-dimensional problem.
Flexbox distributes space along a single line, either a row or a column. Grid places items on rows and columns at once, and the rows and columns relate to each other. Most Grid-vs-Flexbox confusion comes from skipping that distinction.
Flexbox is one-dimensional, Grid is two-dimensional
Flex items live on one axis, set by flex-direction. With flex-direction: row every child lines up left to right (or right to left, with row-reverse); with column they stack top to bottom. There is a cross axis, and align-items works on it. But Flexbox never asks an item on one line to line up with an item on another. Even when items wrap, each wrapped line is laid out on its own.
| |
Grid items sit in rows and columns you define up front, and you can place and size each item against both axes at the same time.
| |
1fr is a fraction unit. The browser subtracts the fixed tracks (240px) first, then splits what’s left among the fr tracks. So grid-template-columns: 1fr 2fr gives the second column twice the first one’s share of the leftover space, not twice its total width.
How to build a navbar with Flexbox
A navbar is the textbook one-dimensional layout: a logo, some links and a button on one line, with space distributed between them.
| |
| |
justify-content: space-between pins the logo to the left edge and the button to the right, with the link list in the middle. align-items: center keeps all three vertically centered even if the logo is taller than the button. The link list is its own flex container, so its spacing comes from gap and you never touch a margin.
Shrink the viewport and nothing wraps, because flex-wrap defaults to nowrap: the items shrink down to their minimum content width and then overflow the navbar. Add flex-wrap: wrap and the items that don’t fit drop to a second line. That second line is laid out independently and won’t line up under anything on the first. For a navbar, fine. For a layout where rows have to line up, it’s the sign you’ve hit the edge of what Flexbox does.
How to build a dashboard layout with CSS Grid
Named grid areas let you read a page layout without translating row and column numbers in your head.
| |
Each name in grid-template-areas has to form a rectangle, and the strings are a picture of the page: you can see the sidebar spans all three rows by looking at the CSS. The sidebar is a fixed 240px, the header and footer get explicit heights, and main takes whatever is left because its row is 1fr. The footer sits at the bottom of the viewport on a short page with no absolute positioning and no JavaScript, because min-height: 100vh on the container plus a 1fr row already does it.
The Flexbox version of the same page needs two containers: a flex row for sidebar plus content, then a flex column inside the content for header, main and footer, with flex: 1 on main to push the footer down. It works. You’re stacking two one-dimensional layouts to fake a two-dimensional one, and every change to the page structure means touching both.
A responsive card grid with no media queries
This one pattern replaces a lot of media-query boilerplate: a grid of cards that adds or drops columns as the viewport changes.
| |
repeat(auto-fit, minmax(220px, 1fr)) tells the browser to fit as many columns of at least 220px as the row allows, then stretch them to fill it. A 1400px container gets five columns of about 260px; a phone gets one full-width column. No breakpoint anywhere.
auto-fit and auto-fill only differ when you have fewer cards than would fill a row. auto-fit collapses the empty tracks, so the real cards stretch across the whole row. auto-fill keeps the empty tracks, so the cards stay the width they’d have in a full row and the rest of the row is blank. Use auto-fill when card width should stay consistent regardless of count, like a product grid where three results shouldn’t balloon to a third of the page each. Use auto-fit when a half-empty row looks worse than wide cards, like a dashboard with two or three summary tiles.
How to use Grid and Flexbox together
You rarely pick one for a whole page. Grid handles the skeleton; Flexbox handles the one-dimensional content inside each region.
| |
Each .card sits in a grid cell and stretches to the cell’s height, since grid items stretch by default. Inside it, header, body and footer form a single column, so justify-content: space-between puts the footer at the bottom of every card no matter how much body text each one has. That’s the working rule: Grid for anything that has to relate to a row and a column at once, Flexbox for content that runs along one line, nested inside wherever Grid put it.
If you use Grid to switch between a card view and a list view, store the visitor’s choice in localStorage. It’s one setItem call, and the choice survives reloads and closed tabs without a server round trip.
When to use subgrid for nested grids
A grid inside a grid item starts its own set of tracks, unrelated to the parent’s. That breaks as soon as you want a card’s internal rows (image, title, description, price) to line up across a row of cards. Set grid-template-rows: subgrid (or grid-template-columns: subgrid) on the nested grid and it reuses the parent’s tracks instead of defining new ones, so the title row of every card lines up with its neighbors’ even though each card is still its own grid container. Subgrid works in all current major browsers, so you can use it directly instead of forcing equal heights with fixed values.
When Flexbox is the better choice even if Grid could do it
Grid can lay out a single row with grid-auto-flow: column. For content that’s really one-dimensional, though, Flexbox has something Grid doesn’t: flex-grow and flex-shrink let each item claim more or less of the leftover space based on its own content. That’s what you want for a search bar that should take whatever room a fixed-width button and icon leave over. Default to Grid when rows and columns need to relate; use Flexbox when you’re laying out one line and want per-item grow and shrink, not just alignment.
The mistake also runs the other way. People build a multi-column layout from flex-wrap: wrap plus a percentage width on each item, to fake a grid. It holds until the last row has fewer items than the others. Then space-between throws the last two items to opposite edges, or flex-grow stretches them wider than everything above. The browser is still distributing items along lines, not filling a grid. If you’re counting columns, use Grid.
If you still need JavaScript on resize, say to reposition a tooltip next to a card, debounce the handler instead of running it on every resize event the browser fires.
Grid or Flexbox: how to choose
One question decides it: do the items have rows and columns that must line up with each other, or is it one line of items?
| Situation | Use |
|---|---|
| Navbar, button group, toolbar | Flexbox |
| Page skeleton: header, sidebar, main, footer | Grid |
| Responsive card or product grid | Grid (auto-fit/auto-fill + minmax) |
| Centering one element, or evenly spacing a few | Flexbox |
| Form layout with aligned labels and inputs | Grid |
| Content inside a single grid cell (a card’s header/body/footer) | Flexbox, nested inside the grid |
Next time you open a stylesheet for a new layout, sketch the boxes first. If you draw lines that cross, it’s Grid. If every box sits on one line, it’s Flexbox.