Aligning the elements of a card, a form, or a product list on a shared grid, when each block holds a different amount of text, has long been one of the recurring problems in web layout. For years, the answer involved measurement JavaScript, fixed heights, or flexbox hacks. The subgrid value from CSS Grid Level 2 solves this natively: a nested container can now inherit the tracks (rows or columns) defined by its parent, instead of computing its own grid.
The problem subgrid solves
Take a row of product cards: image, title, description, price, button. If each card defines its own internal grid, nothing guarantees that the title of card 1 and the title of card 3 occupy the same track height, because card 3’s description runs two lines longer. The visible result: buy buttons that no longer line up horizontally. Historically, two workarounds existed: setting an arbitrary fixed height (which breaks the moment content changes) or measuring heights in JavaScript after render (costly, and visible as a layout jump). subgrid removes the need for both: the card no longer defines its own tracks, it reuses the parent’s.
Syntax: inherit rather than redefine
The syntax stays close to classic CSS Grid, with one key difference: instead of listing track sizes, you write subgrid on grid-template-columns or grid-template-rows. The element itself must be a grid item spanning several of the parent’s tracks for the inheritance to make sense.
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto auto 1fr auto;
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 5;
grid-template-rows: subgrid;
}
.card__title { grid-row: 2; }
.card__description { grid-row: 3; }
.card__price { grid-row: 4; }
.card__action { grid-row: 5; }
Each .card spans the parent’s five rows via grid-row: span 5, then declares grid-template-rows: subgrid to reuse exactly those five rows as its own internal grid. The card’s children are then positioned on these rows using numbers that match the parent grid, not a locally recomputed one.
Named lines: keeping code readable in a shared grid
Once a grid grows past three or four tracks, raw line numbers (grid-row: 2, grid-row: 4) become hard to read, especially with several developers touching the same components. CSS Grid lets you name tracks, and those names automatically propagate to subgrid children, which avoids redeclaring them at every nesting level.
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows:
[media-start] auto
[title-start] auto
[description-start] auto
[price-start] 1fr
[action-start] auto
[action-end];
gap: 1.5rem;
}
.card {
display: grid;
grid-row: media-start / action-end;
grid-template-rows: subgrid;
}
.card__title { grid-row: title-start; }
.card__price { grid-row: price-start; }
The benefit isn’t purely cosmetic: if a row is inserted into the parent grid, the names avoid having to manually renumber every child component referencing the following tracks.
Browser compatibility
The feature is now considered Baseline Widely Available, the reference marker for judging that a web feature can be used without reservation across the modern browser landscape.
| Browser | Minimum version supporting subgrid |
|---|---|
| Chrome / Edge | 117 |
| Firefox | 71 |
| Safari | 16.0 |
| Opera | 103 |
| Safari iOS | 16.0 and later |
For projects that still need to support older Chrome or Edge versions (114 to 116, where the feature existed but was disabled by default), wrapping the declaration in a feature query prevents a silent layout break: @supports (grid-template-rows: subgrid) { … }, with a classic grid or flexbox fallback in the default block.
Use cases beyond product cards
“Card board” components (pricing comparisons, team profiles, article grids) benefit from the same treatment as the initial example: the parent grid sets the rhythm, and each child simply occupies the cells that correspond to it without ever recomputing its own geometry.
Forms: a second concrete use case
Long forms made of several field groups (contact details, address, payment) suffer from the same problem as product cards: without a shared grid, nothing guarantees that labels, inputs, and error messages line up on a common column from one group to the next. By declaring a column grid at the form level and letting each fieldset inherit those columns via grid-template-columns: subgrid, the “ZIP code” label and the “Card number” label end up aligned on the same column, even though one belongs to a three-field group and the other to a two-field group. The same mechanism aligns error messages on a dedicated column, whether or not they’re present on each field, without reserving an unsightly empty space when a field is valid.
With subgrid, an interface’s alignment stops depending on the longest content in a row: the parent grid sets the rhythm, not the most verbose text.
Common pitfalls
Three mistakes come up most often in code review. First, forgetting that the nested element must span several of the parent’s tracks (grid-row: span n or grid-column: span n): without that, subgrid has nothing to inherit and the element behaves as a single-track grid. Second, mixing subgrid on one axis with explicit dimensions on the other, which is perfectly valid but confuses readers of the code if a non-trivial case isn’t commented. Finally, stacking several levels of nested subgrid: support exists, but every extra level complicates debugging in DevTools and makes it harder to reason about where a track actually originates.
The gain goes beyond visual comfort and holds up in practice, especially for teams already using container queries for adaptive components, since the two features combine naturally — one driving the container’s size, the other its internal alignment. Paired with well-structured design tokens for spacing, subgrid makes it possible to build page grids entirely driven by a small set of variables, without duplicating track values at every nesting level.
Key takeaways
Subgrid doesn’t add a spectacular new capability to CSS Grid: it removes a limitation. Nested grids now inherit their parent’s tracks instead of recomputing them, which eliminates an entire class of alignment hacks for cards, forms, and lists with variable content height. With browser support now broad, adoption mostly comes down to whether a fallback for a residual audience on older versions is still needed.
This is the kind of CSS feature that doesn’t make noise but genuinely changes day-to-day work: on my client projects, subgrid let me delete dozens of lines of height-measurement JavaScript I’d been dragging along for years on card grids. I’d recommend introducing it progressively, component by component, rather than rewriting an entire page grid at once: alignment regressions are far easier to spot that way. — Simon Janvier
Further reading: the specification and reference examples are detailed on MDN’s Subgrid page.
