Three pricing cards, side by side. One title wraps to two lines, the others don't. One plan has a longer blurb. Suddenly your three "Choose plan" buttons sit at three different heights and the row looks broken.
For years the fixes were all bad: hard-code a fixed height and clip long content, or wire up JavaScript to measure the tallest card and copy its height onto the rest. Tailwind subgrid kills both. With grid-rows-subgrid a card inherits its parent grid's row tracks, so every title, body, and footer lines up across the row — no JS, no magic numbers. It's the same native-CSS-beats-JavaScript move as auto-growing textareas with field-sizing.
What Tailwind subgrid actually does#
A normal nested grid defines its own tracks. They have no relationship to the grid it sits inside, which is exactly why nested content never lines up. Subgrid changes that: a grid item that is also a grid container can opt into its parent's tracks instead of inventing new ones.
Two utilities turn it on. grid-cols-subgrid adopts the parent's column tracks; grid-rows-subgrid adopts its row tracks. They compile to grid-template-columns: subgrid and grid-template-rows: subgrid — nothing more.
Here's the column version, straight off the spec. A child spans three columns and becomes a subgrid, so its children snap to the parent's column lines:
<!-- Parent: four equal columns -->
<div class="grid grid-cols-4 gap-4">
<div>01</div>
<div>02</div>
<!-- Spans columns 2–4 AND adopts those exact tracks -->
<div class="col-span-3 grid grid-cols-subgrid gap-4">
<div class="col-start-2">Snaps to the parent's third column</div>
</div>
</div>
For card layouts the interesting axis is rows, so the rest of this is grid-rows-subgrid.
Build aligned card grids with grid-rows-subgrid#
The trick is to model each card as three rows — title, body, footer — and have every card share the same three rows from the parent. You get there in two moves: leave the parent's rows implicit, then make each card span three of them as a subgrid.
{{-- Parent defines the columns. Rows stay implicit. --}}
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
@foreach ($plans as $plan)
{{-- row-span-3 claims three rows; grid-rows-subgrid adopts them --}}
<article class="row-span-3 grid grid-rows-subgrid gap-6 rounded-xl border border-zinc-200 p-6 dark:border-zinc-800">
<h3 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
{{ $plan->name }}
</h3>
<p class="text-sm text-zinc-600 dark:text-zinc-400">
{{ $plan->description }}
</p>
<a href="{{ route('plans.show', $plan) }}"
class="inline-flex justify-center rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white">
Choose {{ $plan->name }}
</a>
</article>
@endforeach
</div>
Two classes do the work. row-span-3 places each card across three of the parent's implicit rows. grid grid-rows-subgrid then makes the card a grid whose three rows are those parent rows — not three new ones. The card's three children drop onto the shared lines.
Because every card in a row sits on the same three lines, the first row grows to fit the tallest title, the second to fit the tallest body, the third to fit the tallest footer. Nothing is measured. The browser solves the nested grid alignment for you.
One rule to internalise: the count has to match. Three rows spanned, three direct children. Add a fourth child and it spills into a new implicit row inside the card, breaking the alignment you just bought.
Align headers and footers across the row#
This is where it pays off. The body row stretches to the tallest description in the row, so short-blurb cards end up with empty space above their footer — and the footer still sits on the third shared line, dead level with every other card's footer. That is exactly the aligned card headers and footers people used to fake with flex plus mt-auto, except it survives content of any length without a single utility doing the pushing.
The pattern reflows cleanly too. At sm: the grid drops to two columns, at lg: it goes to three, and each new band of cards re-forms its own three shared rows. If your cards live somewhere with no fixed viewport relationship — a card that's full-width in a sidebar but three-up in the main column — reach for component-first responsive design with container queries and swap the sm:/lg: prefixes for @ variants. Subgrid and container queries compose without fuss.
If the accent colour or button class is driven by data inside that @foreach, remember Tailwind's scanner only sees literal strings. Build a class like bg-{{ $plan->color }}-600 and it gets purged — safelist it with @source inline() so the utility actually ships.
Gotchas and edge cases#
Gap is inherited. A subgrid pulls the parent's gap into its own tracks by default — set gap-6 on the parent and every card gets six units between title, body, and footer too. That is usually fine, but when you want tighter internal spacing, override it on the card with a different gap-* (for example gap-3) or zero it with gap-y-0 and manage spacing yourself.
The card must be a grid item and a grid. grid-rows-subgrid does nothing unless the element carries grid and is itself placed inside a parent grid. Dropping the grid class on the card is the number-one silent failure — the subgrid keyword is simply ignored and you are left wondering why nothing aligned.
Browser support is a non-issue now. CSS subgrid reached Baseline Widely Available on 15 March 2026: Chrome and Edge 117+, Firefox 71+, Safari 16+, north of 92% of users globally. You can ship it without a fallback in any modern project. If you still support ancient engines, gate the rules behind @supports not (grid-template-rows: subgrid) and degrade to a flex column with mt-auto on the footer — slightly ragged, but functional.
@supports not (grid-template-rows: subgrid) {
.plan-card { display: flex; flex-direction: column; }
.plan-card > a { margin-top: auto; } /* rough footer pin for legacy */
}
Wrapping up#
Delete the height-matching JavaScript and the hard-coded h-[420px] hacks. A parent grid with implicit rows, plus row-span-3 grid grid-rows-subgrid on each card, gives you card grids that align perfectly and stay aligned as content changes. Keep children equal to spanned rows, mind the inherited gap, and you're done.
From here, two natural next steps: lean on container queries for component-first responsive design so these cards adapt to their container rather than the viewport, and once they're aligned, add a 3D hover tilt with Tailwind's transform utilities for some depth. Still on Tailwind v3? The official v3-to-v4 upgrade tool gets you onto the current utility set in an afternoon.
FAQ#
What is subgrid in Tailwind CSS?
Subgrid is a CSS Grid feature, exposed in Tailwind through the grid-cols-subgrid and grid-rows-subgrid utilities, that lets a nested grid inherit its parent grid's column or row tracks instead of defining its own. The practical payoff is alignment: content inside nested grid items lines up with the outer grid's lines, which is what makes card titles, bodies, and footers sit level across a row.
How do I use grid-cols-subgrid?
Put grid-cols-subgrid on an element that is both a grid item and a grid container, then give it a col-span-* so it covers several of the parent's columns. Its own children will snap to those inherited column tracks, and you can position them with col-start-*. The row equivalent, grid-rows-subgrid, works the same way with row-span-* for vertical alignment.
How do I make card heights and footers align in a CSS grid?
Model each card as a fixed number of rows — typically title, body, footer — and have every card share the same rows from the parent. Give the parent a column count and leave its rows implicit, then add row-span-3 grid grid-rows-subgrid to each card. The shared rows size to the tallest content in each band, so footers land on the same line regardless of how long any individual card's text is.
Is subgrid supported in all browsers?
Yes, for practical purposes. CSS subgrid became Baseline Widely Available in March 2026 and is supported in Chrome and Edge 117+, Firefox 71+, and Safari 16+, covering well over 90% of users. You can use it in production today; only projects still targeting very old browser versions need a @supports fallback.
What is the difference between grid and subgrid?
A regular grid defines its own track sizes that bear no relation to any grid it is nested inside. Subgrid tells a nested grid to adopt its parent's tracks instead, so the two grids stay aligned on the same lines. Use a plain grid when a component should lay out independently, and subgrid when nested content must line up with the structure around it.