Subgrid vs an Independently Nested Grid

The narrow problem

You have a row of cards. Each card contains a title, a body, and a footer, and each card is itself display: grid so those three parts stack cleanly. Everything looks correct until real content arrives: one title runs to two lines, one body is a sentence longer than the rest, and now the footers sit at three different heights across the row. Making the cards equal height was easy — stretch does that for free. Making the bands inside them line up is the part that a nested grid cannot do, no matter how you size its tracks. This page compares the two constructions directly, states the guarantee subgrid provides, and gives the criteria for choosing between them. It belongs to CSS Grid and Subgrid Layouts within Mastering Container Queries & Responsive Layouts.

Why the distinction is structural, not stylistic

A grid container is normally an information boundary. Its track sizing algorithm looks at its own items and nothing else, and its items are invisible to any grid outside it. That isolation is deliberate and mostly desirable — it is what makes grids composable — but it means two sibling grids have no channel through which to agree on anything. Give them identical grid-template-rows: auto 1fr auto and they will still resolve those auto tracks to different pixel heights, because each measures different content.

subgrid punches a controlled hole in that boundary. A child declared grid-template-rows: subgrid stops defining rows of its own and instead adopts the parent's row lines across the range it spans. Its items are then fed into the parent's track sizing algorithm. Because every sibling card is spanning the same parent rows, the parent sizes each band to the tallest contribution from any card, and all cards inherit that result. The alignment is not approximated; it falls out of a single sizing pass.

The alternatives all trade correctness for convenience. Fixed min-height per band is a guess that content will eventually break. Flexbox with align-items: stretch equalises the cards but not their interiors. Reading heights in script and writing them back works and is what the pre-2023 web did, but it costs a forced synchronous layout on every resize and reintroduces a class of bug — stale measurements after a font swap — that CSS simply does not have.


What each construction guarantees

Subgrid row lines versus independent nested rows On the left, three cards share two dashed horizontal lines because they subgrid onto the parent rows; on the right the same three cards have separators at six different heights. the same three cards, two constructions subgrid nested grid two shared lines six unrelated lines parent sizes the bands once each card sizes its own bands

Stated precisely: subgrid guarantees that items in different children which span the same parent track occupy that track's resolved size. That is a guarantee about lines, not about content, which has two useful consequences. Alignment survives arbitrary content length, and it survives reordering — move a card and its bands still land on the same lines, because the lines belong to the parent.

A nested grid guarantees only that its own items are laid out on its own tracks. It cannot see a sibling, so it cannot match one. The commonly attempted workarounds fail for instructive reasons: 1fr rows distribute the card's free space, which differs per card; align-content: space-between pins only the first and last bands and lets the middle drift; and grid-auto-rows on the parent affects the cards themselves, not their interiors.

The demo shows both at once. Drag the frame narrower until a title wraps — the subgrid row keeps its bands locked while the nested row goes ragged.

Live demoSubgrid cards versus independently nested grid cards
Drag the frame: the top row keeps its title, body and footer bands locked to shared lines; the bottom row goes ragged the moment a title wraps. Drag the bottom-right corner to resize the frame in either direction.

Complete working implementation

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Subgrid versus nested grid</title>
<style>
  body { font: 16px/1.4 system-ui, sans-serif; margin: 2rem; color: #1c2430; }
  .head { margin: 0 0 0.5rem; font: 0.75rem ui-monospace, monospace; color: #666; }

  .deck {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.75rem;
    margin-bottom: 1.75rem;
  }

  .card {
    display: grid;
    border: 1px solid #d4dbe8;
    border-radius: 8px;
    background: #fff;
    padding: 0.6rem 0.7rem;
    row-gap: 0.4rem;
  }
  .card h3 { margin: 0; font-size: 0.95rem; }
  .card p { margin: 0; color: #5a6472; font-size: 0.85rem; }
  .card footer {
    padding-top: 0.35rem;
    font-size: 0.8rem;
    border-top: 1px solid #e6eaf2;
    align-self: end;   /* pin the footer to the bottom of its band */
  }

  /* --- Construction A: subgrid ------------------------------------ */
  /* The parent declares the three bands ONCE. Because the deck has one
     explicit row of cards, these become the implicit row pattern. */
  .deck--sub { grid-auto-rows: auto 1fr auto; }

  /* Each card claims three parent rows and adopts their sizes rather
     than computing rows of its own. */
  .deck--sub .card {
    grid-row: span 3;
    grid-template-rows: subgrid;
  }

  /* --- Construction B: independent nested grid --------------------- */
  /* Identical track list, but resolved separately inside every card,
     so the resulting pixel heights differ card to card. */
  .deck--nested .card { grid-template-rows: auto 1fr auto; }

  /* Baseline for engines without subgrid: construction B's behaviour. */
  @supports not (grid-template-rows: subgrid) {
    .deck--sub .card { grid-row: auto; grid-template-rows: auto 1fr auto; }
  }
</style>
</head>
<body>
  <h2 class="head">grid-template-rows: subgrid</h2>
  <div class="deck deck--sub">
    <article class="card"><h3>Latency</h3><p>Median round trip.</p><footer>p95 410ms</footer></article>
    <article class="card"><h3>Requests per second by region</h3><p>Rolling five minute mean.</p><footer>peak 8.1k</footer></article>
    <article class="card"><h3>Errors</h3><p>All 5xx.</p><footer>0.4%</footer></article>
  </div>

  <h2 class="head">independent nested grids</h2>
  <div class="deck deck--nested">
    <article class="card"><h3>Latency</h3><p>Median round trip.</p><footer>p95 410ms</footer></article>
    <article class="card"><h3>Requests per second by region</h3><p>Rolling five minute mean.</p><footer>peak 8.1k</footer></article>
    <article class="card"><h3>Errors</h3><p>All 5xx.</p><footer>0.4%</footer></article>
  </div>
</body>
</html>

Key technique: the span is half the mechanism

grid-template-rows: subgrid on its own is almost always a bug. It says "use the parent's lines for the rows I occupy" — and by default a card occupies exactly one row, so it inherits a single line pair and all three of its children pile into that one band. The span is what widens the range of adopted lines, which is why the two declarations only make sense together.

The same reasoning tells you which axis to subgrid. Ask what needs to align across elements. Bands stacked vertically across a horizontal row of cards means rows. A form where every row's label column must be the same width means columns, and then the child subgrids grid-template-columns and spans columns instead. Subgridding an axis you do not need alignment on costs you the ability to define your own tracks there for nothing in return, which is why one-axis subgrids are the common production shape.


Variation: subgrid one axis, keep the other local

A card that must align its bands with siblings and run a two-column internal layout can do both, because the two axes are independent.

.deck--sub .card {
  grid-row: span 3;
  grid-template-rows: subgrid;      /* rows come from the parent */
  grid-template-columns: 3rem 1fr;  /* columns are the card's own */
}

/* An icon in the first local column, text in the second. */
.card h3       { grid-column: 2; }
.card p        { grid-column: 2; }
.card .icon    { grid-column: 1; grid-row: 1; }
.card footer   { grid-column: 1 / -1; }

Note the gap interaction, which surprises people: on a subgridded axis the child ignores its own row-gap and inherits the parent's, because the gaps are part of the track geometry it adopted. On the non-subgridded axis, the child's own column-gap applies as normal.

Markup and performance tradeoffs

Subgrid constrains your markup in one specific way: the card's aligned children must be direct children of the subgridded element. Wrapping the title and body in a <div> for styling convenience removes them from the grid and breaks the alignment, so a subgrid layout tends to have flatter markup than the equivalent nested one. That is usually an improvement, but it does mean the two constructions are not swap-in replacements for each other once wrappers exist.

On cost: a nested grid resolves in isolation, while subgrid track sizing must gather contributions from items in several grids before it can size a band, so it is a slightly heavier layout operation. The scale matters more than the constant — a deck of a dozen cards is unmeasurable, a virtualised table of thousands of subgridded rows is worth profiling. Compared against the JavaScript equalisation it replaces, subgrid wins outright, because that approach adds a second layout pass on every resize rather than a modest cost within the first one.

Choose the nested grid when a component's internals are genuinely private — a standalone hero, a modal, a chart's own axis layout. Choose subgrid the moment two or more siblings must agree.

Browser support note

Subgrid is supported in Firefox 71+, Safari 16+, and Chrome and Edge 117+, so every evergreen engine has it as of 2026. Because the failure mode is a graceful loss of alignment rather than a broken layout, a @supports not (grid-template-rows: subgrid) block that restores explicit rows — as in the implementation above — is enough for older builds. Note that @supports (display: grid) tells you nothing about subgrid; test the exact property-value pair.

FAQ

What can subgrid do that a nested grid cannot? Subgrid lets a child participate in its parent's track sizing, so separate siblings share the same row or column lines. A nested grid computes its tracks only from its own contents and can never coordinate with a sibling.

Do I need grid-row: span on a subgrid item? Yes, whenever you subgrid an axis with more than one track. The item must claim the range of parent lines it intends to adopt; without a span it occupies a single track and inherits only that one line pair.

Is subgrid slower than a nested grid? Marginally, because track sizing has to consider items from more than one grid before resolving. In practice it is far cheaper than the JavaScript measuring pass it replaces, and the difference is not observable at typical card counts.

Can I subgrid only one axis? Yes. grid-template-rows and grid-template-columns take subgrid independently, so a card can inherit the parent's rows for cross-card alignment while defining its own columns internally.

Related articles

More pages in the same section.