Skip to main content
Bram Verweij
CSS

CSS Grid Subgrid: A Practical Guide to Nested Layouts

CSS grid layout diagram on a dark developer workspace with blue neon grid lines

You've set up a three-column card grid. Each card has an image, a category label, a title, a short description, and a call-to-action link. The layout looks fine when all the titles are the same length — but as soon as real content arrives, cards in the same row fall out of sync. The title in card one wraps to two lines and pushes the CTA down, while cards two and three stay short. The row looks ragged.

Before CSS Grid Subgrid reached baseline status, fixing this meant hardcoding heights, abusing display: contents, or reaching for JavaScript. Now, with subgrid supported in every major browser since Chrome 117, you can solve it with two CSS properties. Here's exactly how.

The Problem with Nested Grids

Side-by-side comparison: misaligned card grid on the left, perfectly aligned subgrid cards on the right

When you nest a grid inside a grid item, the child creates its own independent track system. The parent grid has no influence over the rows inside each card — they're defined separately and sized by their own content. So when one card's title is longer than its neighbours', its internal rows shift, and cross-card alignment breaks.

The classic workaround was to make each card a direct child of the parent grid, spanning multiple rows — effectively flattening the card's elements into the parent track system. That works for alignment, but it destroys semantic HTML structure: you can no longer wrap each card in a single <article> element with its children grouped naturally inside it.

/* The broken pattern — each card has its OWN independent row sizes */ .card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } .card { display: grid; /* These rows are isolated from .card-grid — no cross-card alignment */ grid-template-rows: auto auto 1fr auto; }

Because .card's row tracks are independent of .card-grid's rows, each card calculates its own row heights. A long title in card one does not affect the title row height in cards two or three — they're on separate grids entirely.

How Subgrid Works

Subgrid lets a grid item inherit its parent's track definitions instead of creating new ones. Set grid-template-rows: subgrid on a grid item that spans multiple parent row tracks, and its children will align to those same parent tracks — as if they were direct children of the parent grid, while still being semantically nested inside their card element.

The key requirement: the child element must span at least two tracks on the axis you want to inherit. A card spanning one column cannot inherit column tracks, but it can inherit row tracks if the parent defines rows that cross the full grid.

/* Minimal subgrid setup */ .card-grid { display: grid; grid-template-columns: repeat(3, 1fr); /* Define 4 shared row tracks across all cards in the same visual row */ grid-template-rows: auto auto 1fr auto; gap: 1rem; } .card { /* Span all 4 row tracks from the parent */ grid-row: span 4; display: grid; /* Inherit those 4 row tracks — don't create new ones */ grid-template-rows: subgrid; }

Now every .card's children sit in the same row tracks as the equivalent children of every other card in the row. When one title wraps to two lines, that row expands for all cards simultaneously, keeping images, categories, titles, and CTAs perfectly in sync across the row.

Practical Example — Aligned Card Layouts

Setting Up the Parent Grid

Start with semantic HTML: a grid container holding <article> elements, each with its four child elements in document order.

<div class="card-grid"> <article class="card"> <img src="/images/design.jpg" alt="Design article image"> <span class="category">Design</span> <h2>A longer card title that will wrap to two lines in narrow columns</h2> <a href="/en/articles/design" class="cta">Read more</a> </article> <article class="card"> <img src="/images/css.jpg" alt="CSS article image"> <span class="category">CSS</span> <h2>Short title</h2> <a href="/en/articles/css" class="cta">Read more</a> </article> <article class="card"> <img src="/images/perf.jpg" alt="Performance article image"> <span class="category">Performance</span> <h2>Another card title that's a medium length</h2> <a href="/en/articles/perf" class="cta">Read more</a> </article> </div>

Applying Subgrid to Each Card

The complete working CSS. The parent grid defines both column and row tracks. Each card spans one column but all four row tracks, then declares grid-template-rows: subgrid to inherit them. The card's children automatically land in the inherited tracks — no extra placement rules needed.

.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); /* 4 named rows: image | category label | title | CTA */ grid-template-rows: 200px auto 1fr auto; gap: 1.5rem; } .card { /* Span all 4 row tracks defined on the parent */ grid-row: span 4; display: grid; /* Inherit parent row tracks — children align across cards */ grid-template-rows: subgrid; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; } /* Each child lands in the next inherited row track automatically */ .card img { width: 100%; height: 100%; object-fit: cover; } .card .category { padding: 8px 16px 0; font-size: .8em; color: #64748b; } .card h2 { padding: 4px 16px; margin: 0; } .card .cta { padding: 0 16px 16px; align-self: end; }

With this setup: the image row is always 200 px, the category row expands to the tallest category label across the row, the 1fr title row absorbs the longest title, and the CTA row is always flush to the bottom of each card — regardless of how much content each card holds.

Named Grid Lines with Subgrid

Named grid lines defined on the parent are inherited by subgrid children automatically. This lets you reference meaningful names in child CSS rather than relying on implicit numeric placement:

.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: [img-start] 200px [img-end] [cat-start] auto [cat-end] [title-start] 1fr [title-end] [cta-start] auto [cta-end]; } .card { grid-row: span 4; display: grid; grid-template-rows: subgrid; } /* Explicit placement using inherited names — readable and robust */ .card img { grid-row: img-start / img-end; } .card .category { grid-row: cat-start / cat-end; } .card h2 { grid-row: title-start / title-end; } .card .cta { grid-row: cta-start / cta-end; }

Named lines are especially useful in design systems where the track layout is defined once in a shared stylesheet and consumed in many components. Partial subgrid — inheriting rows only while leaving columns independent, or vice versa — is fully valid. See the MDN Subgrid reference for the full named-line inheritance spec.

Feature Detection and Browser Support

Subgrid is supported across all major browsers and is considered baseline. For projects that still need to reach older browser versions, wrap the subgrid-specific rules in an @supports block:

/* Fallback: independent row sizing — layout still works, just not aligned */ .card { display: grid; grid-template-rows: 200px auto 1fr auto; } /* Progressive enhancement: inherit parent tracks when supported */ @supports (grid-template-rows: subgrid) { .card-grid { grid-template-rows: 200px auto 1fr auto; } .card { grid-row: span 4; grid-template-rows: subgrid; } }

Browser support as of 2026:

Browser Subgrid support since Notes
Chrome / Edge 117 (Aug 2023) Both columns and rows
Firefox 71 (Dec 2019) First browser to ship subgrid
Safari 16 (Sep 2022) Both columns and rows

For current usage data, check caniuse.com/css-subgrid. With Chrome 117+ covering the last holdout, subgrid is safe to use in production without a fallback for most audiences. The @supports guard is still worth adding for any project targeting users on long-term-support enterprise environments.

Debugging Subgrid in DevTools

Getting subgrid wrong is easy — and the visual symptoms (misaligned elements, unexpected collapsing rows) look identical to other grid bugs. DevTools grid overlays are your first diagnostic tool.

Chrome / Edge: Open DevTools, select the parent grid container, and click the grid badge in the Elements panel to enable the grid overlay. Then select the subgrid child and enable its overlay too. You'll see both sets of tracks rendered simultaneously — the inherited tracks highlighted differently from any tracks the child would define independently. This immediately shows whether the subgrid relationship is active.

Firefox: Still the best subgrid visualisation. The Layout panel shows named lines with their labels directly on the overlay, making it easy to confirm that inherited names are resolving correctly.

Two quick diagnostic checks when subgrid isn't behaving:

  1. Verify the span. The child must have grid-row: span N (or explicit placement covering N rows) where N matches the number of row tracks the parent defines in that section. A span of 3 on a 4-row subgrid leaves one track unaccounted for.
  2. Confirm the child is a direct grid item. Subgrid only works when the element declaring grid-template-rows: subgrid is itself a direct child (grid item) of the parent grid. An extra wrapper element between the parent grid container and the subgrid element breaks the relationship entirely.

When Not to Use Subgrid

Subgrid is the right tool for cross-sibling alignment across nested containers — the card layout above being the canonical case. It's not always the best choice:

  • Single-axis alignment within a single container. If you just need the items inside one flexbox row to stretch to the same height, align-items: stretch (the Flexbox default) handles it without any grid involvement.
  • Deep nesting beyond one level. Subgrid inherits tracks one level at a time. A grandchild can subgrid from its parent, which subgrids from its grandparent — chaining works — but it adds cognitive overhead. If you're three levels deep and still fighting alignment, reconsider the component structure.
  • Dissolving a single wrapper. display: contents makes an element "transparent" to the grid, so its children become direct grid items of the grandparent. It's a narrower tool than subgrid but useful when you have a single semantic wrapper (like a <div> added for JavaScript purposes) that you don't want to influence layout. See the web.dev subgrid article for a direct comparison of these two approaches.

Subgrid is now production-safe and widely supported. The card layout pattern above will save meaningful time on almost any project that mixes variable-length content in a grid of repeating components. Start with grid-template-rows: subgrid for quick wins — you often only need to inherit the row axis, leaving each card free to define its own column structure independently.

More from the blog

All posts