Exploring CSS Subgrid: Finally, True Nested Layouts

Team 6 min read

#css

#subgrid

#css-grid

#layouts

CSS Grid unlocked powerful two‑dimensional layouts, but one pain remained: nested components rarely aligned to the parent grid without duplicating track definitions. CSS Subgrid fixes that. It lets a child grid inherit the tracks and gaps of its parent grid along one or both axes, enabling precise alignment for true nested layouts.

This article explains how subgrid works, when to reach for it, common gotchas, and how to progressively enhance your CSS.

Why subgrid matters

  • Align nested content without duplicating track sizes across components.
  • Keep consistent gutters and rhythm across complex pages.
  • Build editorial layouts, dashboards, forms, and card grids where inner pieces align to outer columns.

High‑level mental model

  • The parent defines tracks (columns and/or rows).
  • A child becomes a grid and says “for columns and/or rows, use subgrid.”
  • The child then shares the parent’s tracks and gaps for that axis.
  • The number of tracks available in the subgrid equals the number of parent tracks the child spans.

Core syntax

  • On the child grid:
    • grid-template-columns: subgrid; and/or
    • grid-template-rows: subgrid;
  • Ensure the child spans the intended parent tracks:
    • grid-column: 1 / -1; and/or grid-row: 1 / -1;

Basic example: columns subgridded

HTML

<section class="layout">
  <article class="card">
    <h2 class="title">Article title</h2>
    <p class="meta">By Jane Doe</p>
    <p class="excerpt">Short introduction paragraph that should align with the main grid columns.</p>
  </article>
</section>

CSS

.layout {
  display: grid;
  grid-template-columns:
    [full-start] minmax(1rem, 1fr)
    [content-start] repeat(8, minmax(0, 1fr))
    [content-end] minmax(1rem, 1fr)
    [full-end];
  gap: 1rem;
}

/* The card is itself a grid that shares the parent’s column tracks */
.card {
  display: grid;
  /* Make sure the card spans the content columns you want to reuse. */
  grid-column: content-start / content-end;

  /* Inherit columns and gaps from the parent for the column axis */
  grid-template-columns: subgrid;

  /* You can define independent rows locally */
  grid-template-rows: auto auto 1fr;
  row-gap: 0.5rem; /* Row gap is local */
}

/* Place inner elements on the inherited columns */
.title {
  grid-column: 1 / span 6; /* Aligns to the same 8-column content grid */
}
.meta {
  grid-column: 7 / -1;
}
.excerpt {
  grid-column: 1 / -1;
}

Key points

  • The card uses the parent’s column tracks because of grid-template-columns: subgrid.
  • The card must span the parent’s columns it wants to inherit (grid-column: content-start / content-end).
  • The parent’s column gap applies within the card for the column axis; you can still set independent row gaps on the card.

Row subgrid: aligning rows across cards

HTML

<ul class="cards">
  <li class="card">
    <h3 class="title">Card A</h3>
    <p class="body">Short body text.</p>
    <button class="cta">Read more</button>
  </li>
  <li class="card">
    <h3 class="title">Card B with a longer title</h3>
    <p class="body">Longer text that wraps and makes the card taller.</p>
    <button class="cta">Read more</button>
  </li>
</ul>

CSS

.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(12rem, 1fr));
  grid-template-rows: auto auto 1fr auto; /* title, body, spacer, actions */
  gap: 1rem 1rem;
}

.card {
  display: grid;
  /* Span one column in the parent; rows will be subgridded */
  grid-auto-flow: row;
  grid-template-rows: subgrid; /* rows are shared with the parent */
  grid-row: 1 / -1; /* ensure the item spans all rows you want to reuse */
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 0.5rem;
  background: white;
}

.title { grid-row: 1; }
.body  { grid-row: 2; }
.cta   { grid-row: 4; justify-self: start; }

Result: titles, body text, and CTAs align perfectly across cards regardless of content height.

Using and adding line names You can name lines on the parent and reference those names inside the subgrid. You can also add additional names at the subgrid boundary.

.wrapper {
  display: grid;
  grid-template-columns:
    [full-start] 1fr
    [content-start] repeat(8, [col] minmax(0, 1fr))
    [content-end] 1fr
    [full-end];
  column-gap: 1rem;
}

.section {
  display: grid;
  grid-column: content-start / content-end;
  /* Add extra names on top of inherited ones */
  grid-template-columns: [content-start] subgrid [content-end];
}

/* Inside .section you can use col line names from the parent */
.section > .aside {
  grid-column: col 1 / col 3;
}
.section > .main {
  grid-column: col 3 / col 8;
}

Common gotchas

  • Must span tracks: The subgrid item only has as many tracks as it spans on that axis. To inherit all content columns, use grid-column: content-start / content-end (or 1 / -1).
  • Gaps come from the parent for the subgridded axis: You cannot override column-gap inside the subgrid when columns are subgridded. Set gap on the parent or subgrid the other axis.
  • Auto tracks: grid-auto-rows/columns on the subgrid container don’t apply to the subgridded axis. Define them on the parent if needed.
  • Areas: Subgrid doesn’t define new track sizing on the subgridded axis, so grid-template-areas is limited there. Prefer line-based placement or define areas on the parent.
  • Intrinsic sizing: Content-based track sizing (min-content/max-content) in the parent affects the subgrid. If something overflows unexpectedly, inspect parent track sizing and minmax() values.

Progressive enhancement and fallbacks Subgrid has strong cross‑browser support today, but you can still provide fallbacks.

Strategy 1: Duplicate track list as a fallback

:root {
  --content-columns: repeat(8, minmax(0, 1fr));
  --content-gutter: 1rem;
}

.layout {
  display: grid;
  grid-template-columns:
    1fr var(--content-columns) 1fr;
  column-gap: var(--content-gutter);
}

.card {
  display: grid;

  /* Fallback: use the same custom property tracks */
  grid-template-columns: var(--content-columns);

  /* Enhancement when supported: inherit from parent */
  @supports (grid-template-columns: subgrid) {
    grid-column: 2 / -2; /* match the content column span */
    grid-template-columns: subgrid;
  }
}

Strategy 2: Flexbox fallbacks for simple stacks For components that don’t need strict column alignment, provide a flex layout first, then enhance with subgrid inside @supports.

Browser support

  • Firefox 71+
  • Safari 16.4+
  • Chromium-based browsers (Chrome, Edge, Opera) 117+ In practice, subgrid is available in all modern evergreen browsers. DevTools in major browsers visualize subgrid tracks and line names, which helps debugging.

When to use subgrid

  • Page shells with global content columns, where headers, hero sections, and footers should align to the same grid.
  • Article layouts, editorial modules, and marketing pages with consistent baselines and gutters.
  • Card collections where titles, media, and CTAs should line up across varying content.
  • Complex forms where labels and fields need consistent column alignment across nested groups.

Quick checklist

  • Decide which axis needs alignment (columns, rows, or both).
  • Ensure the subgrid item spans the parent tracks you want to inherit.
  • Use subgrid for that axis and rely on parent gaps.
  • Use parent line names within the subgrid for readable placement.
  • Add @supports fallbacks if you must support older browsers.

Conclusion CSS Subgrid delivers what nested grids always needed: shared tracks and shared rhythm. It removes duplication, simplifies component CSS, and keeps complex layouts consistent. With broad browser support, it’s ready for production. Start by defining a robust parent grid, then pass those tracks into child components with subgrid to get true nested alignment.