ESC

Type to search the knowledge base.

Subgrid

grid-template-rows/columns: subgrid — inherit parent tracks so nested cards and forms share one alignment system.

advanced3 min read
  • css
  • subgrid
  • grid

Nested grids usually create independent track lists. subgrid lets a nested grid reuse its parent’s rows or columns, so titles, prices, and buttons across cards line up even when content heights differ per section.

Docs: MDN subgrid, grid-template-columns.

The problem subgrid solves

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.card {
  display: grid;
  grid-template-rows: auto 1fr auto; /* each card’s 1fr is local */
}

Without subgrid, “equal body areas” only work within each card’s own tracks — not across a row of cards. Aligning the button row across cards is painful.

subgrid rows

.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto auto; /* shared conceptual bands if spanning */
  gap: 1rem;
}

/* Better pattern: parent defines rows; children span and subgrid */
.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}

.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  gap: inherit; /* optional — subgrid can inherit parent gaps depending on setup */
  border: 1px solid var(--border);
  padding: 1rem;
}

.card__title { /* row 1 of the subgrid */ }
.card__body  { /* row 2 */ }
.card__footer { /* row 3 */ }

Exact markup patterns vary; the key is: child spans N parent tracks and sets grid-template-rows: subgrid (or columns) so its children occupy those same tracks.

subgrid columns for forms

.form {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 1rem;
  row-gap: 0.75rem;
}

.form__group {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

Nested field groups stay on the same label/control columns as the outer form.

Gaps and naming

Subgrid can inherit parent gap sizes; you can also set gaps on the subgrid. Named lines on the parent can be referenced by subgridded children depending on the engine’s implementation of line names — keep names simple and test.

Support and fallbacks

Subgrid is supported in current major engines; still define a non-subgrid layout for older targets if required:

.card {
  display: grid;
  grid-template-rows: auto 1fr auto;
}

@supports (grid-template-rows: subgrid) {
  .card {
    grid-row: span 3;
    grid-template-rows: subgrid;
  }
}

Interview out-loud

“Subgrid makes a nested grid use its parent’s track definitions so nested items align to the same rows or columns. The child spans parent tracks and sets grid-template-rows: subgrid or grid-template-columns: subgrid. It’s ideal for card matrices and multi-level forms. Without it, each nested grid sizes tracks independently.”

Footguns

  1. Forgetting the child must span the parent tracks it wants to share.
  2. Expecting subgrid to equalize columns across separate parent grids.
  3. Deep nesting without @supports fallbacks when required.
  4. Mixing dense auto-placement with subgrid spans until placement gets confusing.
  5. Assuming gap inheritance without verifying.

Card alignment demo intent

Imagine three cards in a row: title, body, price, button. Without subgrid, the buttons sit at different heights when body text wraps unevenly. With parent rows shared via subgrid, price and button bands align across cards — the classic catalog UI problem. When support is missing, fall back to equalizing with fixed min-heights sparingly, or accept natural height variance (often fine for content-first blogs).

.catalog {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}
@supports not (grid-template-rows: subgrid) {
  .card__body { min-height: 6rem; } /* compromise */
}

Further reading

Related guides