Quick take: - CSS subgrid lets a child grid reuse the row or column tracks defined on its parent instead of building its own. Set
grid-template-columns: subgridorgrid-template-rows: subgridon the child, and its contents snap to the parent's lines. That's how you get titles, body copy, and footers to line up across every card in a grid. Support is above 90% in 2026.
CSS subgrid solves one specific, annoying problem: nested grids don't share track lines with their parent, so content inside separate children drifts out of alignment. You set grid-template-rows: subgrid (or columns) on a grid item that is itself a grid, and its children start measuring against the parent's tracks. Subgrid is Baseline widely available and sits above 90% global support as of 2026. I reach for it any time a card layout needs pixel-perfect rows without me hard-coding heights.
What problem does CSS subgrid solve?
Picture a product grid. Each card holds a title, a description, and a price footer. The titles are different lengths - some wrap to two lines, some stay on one. The descriptions vary too. Without subgrid, each card is its own little grid, and each card sizes its rows from its own content. The result? Prices float at different heights. Titles push the body down by different amounts. The whole row looks crooked.
Here's the thing. The parent grid does know where the columns are, so the cards themselves line up left to right. But the parent has no idea what's happening inside each card. A child grid's rows are private. That internal misalignment is exactly what subgrid fixes.
/* The outer grid: three columns of cards */
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
/* Each card is a normal grid here - and its rows DON'T match its neighbours */
.card {
display: grid;
grid-template-rows: auto 1fr auto;
}
That grid-template-rows: auto 1fr auto looks reasonable. But auto means "as tall as this card's title," which differs card to card. So the middle rows never agree.
How does the subgrid keyword work?
The fix is to give the outer grid explicit row tracks, then tell each card to inherit them. You do that with the subgrid keyword. When a grid item spans some of the parent's tracks and sets grid-template-rows: subgrid, it stops inventing its own rows. It adopts the parent's.
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Define the shared rows ON THE PARENT */
grid-template-rows: auto 1fr auto;
gap: 1.5rem;
}
.card {
display: grid;
/* Span all three parent rows, then borrow them */
grid-row: span 3;
grid-template-rows: subgrid;
}
Now every card measures its title, body, and footer against the same three lines. The tallest title in the whole grid sets the height of the first row, and every other card respects it. Same for the body and footer. Notice the parent defines the tracks once, and the children just point at them with a single keyword. Isn't that cleaner than the alternative?
You can do the same on the column axis with grid-template-columns: subgrid, and you can use both at once. Each axis is independent.
A card grid where three rows line up
Let me show the full thing - markup plus CSS - so the alignment is concrete. Three stacked rows (title, body, footer) that agree across all cards.
<div class="product-grid">
<article class="card">
<h3 class="card__title">Compact wide-angle lens for mirrorless bodies</h3>
<p class="card__body">A short description that runs a couple of lines.</p>
<div class="card__footer"><span>$249</span><button>Add</button></div>
</article>
<article class="card">
<h3 class="card__title">Tripod</h3>
<p class="card__body">A much longer description that keeps going and wraps onto three or even four lines depending on the width.</p>
<div class="card__footer"><span>$79</span><button>Add</button></div>
</article>
</div>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-template-rows: auto 1fr auto;
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
border: 1px solid #2a2a2a;
border-radius: 8px;
padding: 1rem;
}
.card__footer {
display: flex;
justify-content: space-between;
align-items: center;
}
The first card has a long two-line title and a short body. The second has a one-word title and a four-line body. Without subgrid, their footers would sit at wildly different heights. With it, the footers line up on the same baseline, and so do the titles. The gap between rows even inherits from the parent's gap, which is a nice touch a plain nested grid can't give you.
Subgrid vs nesting a normal grid
So when do you use which? A plain nested grid is the right call when a component's internal layout is nobody else's business - a settings panel, a form section, a self-contained widget. Its rows and columns should size from its own content, full stop.
Subgrid is the right call the moment children of different parents need to agree with each other. Card grids, comparison tables built from divs, timeline entries, any repeating unit where a shared baseline matters. The rule I use: if misalignment between siblings would look like a bug, reach for subgrid.
Before subgrid landed, I faked this with fixed heights. I'd set min-height: 3.2rem on every title so two-line and one-line titles occupied the same space, then pray nobody wrote a three-line title. It broke constantly. New copy came in, a title wrapped further than I'd budgeted, and the footers drifted again. Subgrid deletes that whole category of hack. My honest opinion? For any card grid with variable-length content, subgrid is worth it even at a small support cost - the fixed-height workaround is more fragile and needs babysitting every time the copy changes. For a one-off layout where nothing repeats, don't bother; a normal grid is simpler.
If you're still deciding between the two-dimensional power of Grid and the one-dimensional simplicity of Flexbox for the outer container itself, the CSS Grid vs Flexbox comparison walks through that decision with concrete examples.
Browser support and a graceful fallback
Subgrid has a longer history than people assume. Firefox shipped it back in version 71 in late 2019. Safari 16 added it in 2022, and Chrome 117 followed in September 2023, which is the moment it became Baseline "widely available." As of 2026, global support sits above 90% according to caniuse and MDN. That's production-ready for most audiences.
Still, 90-something percent isn't 100. Guard it with @supports so older browsers get a sane layout instead of a broken one.
.card {
display: grid;
grid-template-rows: auto 1fr auto; /* fallback: each card sizes itself */
}
@supports (grid-template-rows: subgrid) {
.product-grid {
grid-template-rows: auto 1fr auto;
}
.card {
grid-row: span 3;
grid-template-rows: subgrid; /* the shared-track upgrade */
}
}
Browsers without subgrid keep the plain nested grid. The footers won't be perfectly aligned there, but the cards still stack cleanly and read fine. No JavaScript, no polyfill, no layout explosion. That's a graceful degrade, and for a purely cosmetic alignment feature it's exactly the right trade.
One last practical note. Subgrid only inherits tracks it actually spans, so the child has to grid-row: span N (or grid-column: span N) across the parent lines you want to borrow. Forget the span and the keyword quietly does nothing. Check the MDN subgrid reference if the alignment isn't taking - it's almost always a missing span.
Related
- CSS Container Queries - make each subgrid-aligned card adapt to its own width, not just the viewport