Skip to main content
MilfTravel
CSS

CSS Grid Subgrid: A Practical Guide to Nested Layouts

Diagram showing nested grid elements aligning to a parent CSS grid track layout

CSS Grid lets you build powerful two-dimensional layouts, but nested grids have always had one frustrating limitation: a child element that is itself a grid container creates its own independent track system, completely unaware of the parent's columns and rows. The result is misaligned cards, ragged form fields, and a lot of manual min-height hacks. CSS Grid Level 2 introduces subgrid — a single keyword that lets a nested element inherit and participate in its parent's track definitions, finally solving the alignment problem cleanly.

What is CSS Subgrid?

Normally, when you set display: grid on a child element, it starts a fresh grid formatting context. Its columns and rows have nothing to do with the parent grid's tracks — they are sized independently. Subgrid changes this: by setting grid-template-columns: subgrid or grid-template-rows: subgrid on a grid item that spans multiple tracks, you tell that element to use the parent's track lines instead of defining its own.

The value is part of the CSS Grid Level 2 specification. Browser support landed fully in Chrome 117, Firefox 71, Safari 16, and Edge 117 — so as of late 2026 you can use it without a polyfill in any modern browser. For a live compatibility table, see Can I Use — subgrid.

The Core Syntax

There are two key rules for subgrid to work:

  1. The child must be a grid item of the parent (directly placed or auto-placed into the parent grid).
  2. The child must explicitly span the tracks it wants to inherit — subgrid only applies over the spanned tracks.

Here is the minimal pattern:

/* Parent: a 3-column grid */
.parent {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  gap: 16px;
}

/* Child spans all 3 columns and inherits their sizing */
.child {
  grid-column: 1 / -1;          /* span all 3 parent columns */
  display: grid;
  grid-template-columns: subgrid; /* inherit the 3 tracks */
}

Inside .child, you now place grandchildren using the same column lines as the parent. The gap defined on the parent is also inherited — you don't need to repeat it. If you want the child to also subgrid its rows, add grid-template-rows: subgrid and a matching grid-row: span N.

Real-World Example: Card Grid Alignment

The most common pain point is a grid of cards where each card has three internal zones — a title, a body, and a footer action — and you want these zones to align horizontally across all cards in the same row, regardless of how much text each card contains.

Without subgrid: you either fix card heights (inflexible), or use align-items: stretch plus internal flexbox tricks, which still doesn't align the title bottoms across cards. With subgrid:

/* Grid of 3 cards per row; each card spans 3 implicit rows */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: auto;          /* implicit rows size to content */
  gap: 24px 16px;
}

.card {
  grid-row: span 3;              /* occupy 3 row tracks in the parent */
  display: grid;
  grid-template-rows: subgrid;   /* inherit those 3 row tracks */
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 20px;
}

/* Now each zone aligns across the row automatically */
.card__title  { /* row 1 */ }
.card__body   { /* row 2 */ }
.card__footer { /* row 3 */ }

Before subgrid: tall cards stretch down, short cards leave a gap before the footer — the "Read more" links float at different vertical positions. After subgrid: all three zones in every card share the same row-track height, so titles align at the same baseline, body text fills the middle, and footers lock to the bottom of the row — without a single hardcoded height.

Real-World Example: Form Field Alignment

A two-column form where labels sit in column 1 and inputs in column 2 is straightforward on its own. The problem arises when you group related fields inside a <fieldset>: the fieldset breaks out of the parent grid and starts its own context, so labels and inputs no longer share tracks with the rest of the form.

/* Wrapping form: 2 columns, label | input */
.form {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 12px 16px;
  align-items: baseline;
}

/* Fieldset spans both columns and inherits them */
fieldset {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  border: 0;
  padding: 0;
  gap: inherit;
}

/* Each label/input pair inside the fieldset uses the parent columns */
fieldset label  { grid-column: 1; }
fieldset input  { grid-column: 2; }

You can combine this with grid-template-areas on the parent and reference the same named areas inside the subgrid child — named lines are inherited along with the track sizes.

Browser Support and Progressive Enhancement

Full cross-browser support arrived with Chrome/Edge 117 (September 2023), Firefox 71 (December 2019 — ahead of everyone else), and Safari 16 (September 2022). As of 2026, global support is above 92% according to Can I Use. You are safe to use subgrid as a baseline for modern projects.

For the remaining edge cases, wrap subgrid-dependent rules in a feature query:

/* Fallback: simple stretch layout without cross-card alignment */
.card {
  display: flex;
  flex-direction: column;
}

/* Enhancement: subgrid alignment where supported */
@supports (grid-template-rows: subgrid) {
  .card-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: auto;
    gap: 24px 16px;
  }
  .card {
    grid-row: span 3;
    display: grid;
    grid-template-rows: subgrid;
  }
}

A lighter alternative worth knowing is display: contents: it removes the element's box from the formatting context, effectively making its children direct grid items of the parent. This works for simple cases but loses the element's background, border, and padding — so it doesn't replace subgrid for visually distinct card components.

When to Reach for Subgrid

Subgrid is the right tool when children of a grid item need to participate in the parent's track definitions. A quick checklist:

  • Cross-row alignment across sibling items — titles, bodies, and footers of cards in the same row should line up.
  • Multi-zone components inside a shared grid — form groups, definition lists, data tables where cells in different components must share column widths.
  • Named-area inheritance — you want grandchildren to be placed by the same area names defined on the grandparent grid.
  • Gap inheritance — you want the gutters of the parent to flow through the subgrid without re-declaring them.

If alignment is needed only within a single component (one card, one form group in isolation), align-items: stretch plus display: flex; flex-direction: column is simpler and has universal support. Reach for subgrid specifically when the constraint comes from the parent's track structure, not from within the component itself. For the full property reference, see MDN: CSS Subgrid.