CSS Grid Layouts That Actually Work in 2026 (With Code)

Use CSS Grid for the page shell, not just cards. A three-row body with minmax(0, 1fr) columns stops blowouts, and auto-fit grids reflow cards with no media queries. Grid handles layout, flexbox handles small components.
Part of the complete web design guide.
On this page
- Start with the layout, not the component
- Auto-fit cards without a media query
- Subgrid is the feature that finally landed
- Grid plus container queries for real components
- Named lines and areas: use them, but sparingly
- The accessibility and overflow stuff people skip
- When Grid is the wrong tool
- FAQ
- Do I still need media queries if I use auto-fit?
- Is subgrid safe to use in production in 2026?
- Why does my grid child overflow the column?
Most “grid layout” articles show you the same five-column hero and call it a day. Real sites need sidebars that collapse, cards that reflow without media query soup, and forms that line up their labels without a wrapper div for every field. Here’s what I actually reach for in 2026, plus the parts of Grid that finally have solid browser support and the parts that will still surprise you.
Start with the layout, not the component
The mistake I see most often is people reaching for Grid on a single card and flexbox on the page shell. It’s usually backwards. Grid is a page-level tool first. A whole-site shell takes about eight lines and handles more edge cases than any float or flex setup I wrote in the 2010s.
body {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100dvh;
}
.page {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 65ch) minmax(0, 1fr);
gap: 2rem;
padding-inline: 1.5rem;
}
.page > * {
grid-column: 2;
}
.page > .full-bleed {
grid-column: 1 / -1;
}
That center column with 65ch gives you readable line lengths without guessing at pixel values. The minmax(0, 1fr) on the side columns is the fix for the classic Grid blowout: a long URL or unbroken string in a child will expand a 1fr track past its share unless you cap the minimum at zero. I’ve debugged that one on enough client builds that it’s now muscle memory.
Auto-fit cards without a media query
This is the pattern that replaced about 200 lines of breakpoint logic across projects I maintain. You describe the smallest acceptable card and let the browser figure out how many fit.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
gap: 1.5rem;
}
The nested min() matters. Without it, a 280px minimum breaks on any viewport narrower than 280px plus padding, which still happens on small phones and split-screen modes. With it, the card shrinks gracefully instead of forcing horizontal scroll.
One honest caveat: auto-fit vs auto-fill trips people up. auto-fit collapses empty tracks so your existing cards stretch to fill the row. auto-fill keeps the empty tracks, so cards stay their natural width and you get a gap on the right. For a card list, you almost always want auto-fit. For a calendar or a fixed-column data table, you want auto-fill.
Subgrid is the feature that finally landed
For years, aligning a card’s title, image, and button across a row meant fixed heights or JavaScript. Subgrid solves it properly, and as of 2026 it’s supported in all current major browsers (Chrome, Edge, Firefox, and Safari). If you’re still supporting very old Safari, feature-query it.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 4;
grid-template-rows: subgrid;
}
.card img { grid-row: 1; }
.card h3 { grid-row: 2; }
.card p { grid-row: 3; }
.card a { grid-row: 4; }
Now every card in the row shares the same row tracks, so the headings line up and the buttons sit on the same baseline no matter how long the body copy is. No height: 100% hacks, no flex column with margin-top: auto on the button (though that trick still works fine if you don’t need cross-card alignment).
Grid plus container queries for real components
Container queries let a component respond to its own width instead of the viewport. Combined with Grid, this is how you build a card that looks right in a wide main column and in a narrow sidebar without writing two versions.
.card-wrap {
container-type: inline-size;
}
.card {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@container (min-width: 400px) {
.card {
grid-template-columns: 120px 1fr;
align-items: start;
}
}
Note that container-type: inline-size establishes a containment context, which means the element can no longer size itself based on its contents in the inline direction. That’s usually what you want, but it’s the reason a container-query card sometimes needs an explicit height or aspect ratio on its media.
Named lines and areas: use them, but sparingly
grid-template-areas is genuinely nice for page shells because the CSS reads like the layout. It gets ugly fast on complex pages, and it can’t be reused across different breakpoints without rewriting the whole block. I use it for the top-level shell and stop there.
.shell {
display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 220px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100dvh;
gap: 1rem;
}
@media (max-width: 720px) {
.shell {
grid-template-areas:
"header"
"main"
"footer";
grid-template-columns: 1fr;
}
}
Every named area has to form a rectangle, and every row in the string must have the same number of columns. Miss either one and the whole declaration is dropped silently. If your layout suddenly collapses to a single column with no error in the console, check your area names first.
The accessibility and overflow stuff people skip
- Grid reorders visual position, not DOM order. If you place a visually-first element later in the markup, keyboard and screen reader users hit it out of order. Don’t use grid placement to fix a content-order problem.
minmax(0, 1fr)is your friend. Bare1frhas an automatic minimum ofauto, which lets content push tracks wider than you intended.- Use
gapinstead of margins on grid children. It doesn’t collapse, it doesn’t double up, and it works between tracks you didn’t explicitly name. - For long strings, add
overflow-wrap: anywhereormin-width: 0on the offending child rather than fighting the track sizing. - Test at 320px width with real content. Auto-fit grids look fine with lorem ipsum and fall apart with a real product name and price.
When Grid is the wrong tool
If you’re laying out a single row of items that should wrap and distribute space evenly, flexbox is simpler and reads better. If you need content to define the layout (a tag list, a toolbar), flexbox again. Grid shines when you know the structure and want to control both axes. Reaching for Grid everywhere is its own kind of cargo cult.
If you’re maintaining a site built on an older float or table layout and want to know whether a Grid rewrite is worth it, that’s the kind of thing I look at case by case. I do a lot of Long Island web design work for small businesses, and honestly, the answer is usually “yes for the shell, no for the whole site.” Rewriting a working page just to use newer CSS is rarely a good trade.
FAQ
Do I still need media queries if I use auto-fit?
Less often, but not never. Auto-fit handles column count. It doesn’t handle changes in layout structure, font sizing, or whether a sidebar becomes a top bar. Use both.
Is subgrid safe to use in production in 2026?
Yes for current browsers. If you support old Safari, wrap the subgrid rules in @supports (grid-template-rows: subgrid) and provide a fallback with fixed row heights or flexbox.
Why does my grid child overflow the column?
Almost always a bare 1fr track meeting content with an intrinsic minimum width (a long word, an image, a table). Change the track to minmax(0, 1fr) or set min-width: 0 on the child.


