w3tweaks.com · CSS Tutorial

CSS Subgrid

Nested layouts that lock onto the parent grid — the card fix Grid always needed

Tab 1

The Jagged Buttons Problem — Toggle the Fix

Three cards, wildly different content lengths. With nested grids, each card sizes its rows independently — buttons land at three different heights. Switch to subgrid: every card inherits the parent's row tracks, and titles, meta, bodies, and buttons snap into shared rows.

buttons at 3 different heights — each card computes its own rows

Short title

★ 4.9 · UI Kit

One line of copy.

A much longer title that wraps onto two full lines

★ 4.7 · Template

Medium description that takes a couple of lines to explain what this thing does.

Mid title here

★ 4.8 · Course

The long one: real-world content is never uniform. This body rambles on for several lines — exactly the situation that shattered card alignment for a decade of CSS, forcing min-height hacks and JavaScript equalizers.

/* Parent defines the SHARED row structure */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto 1fr auto; /* title·meta·body·button */
  gap: 14px;
}

/* Each card LOCKS ONTO those rows */
.card {
  display: grid;
  grid-row: span 4; ← span the 4 parent rows
  grid-template-rows: subgrid; ← inherit their sizing
}
How the sizing flows: with subgrid, the tallest title in the row sizes the shared title track; the tallest body sizes the body track. Every card's children participate in sizing the parent's tracks — that's the two-way relationship a nested grid can never have. Row 3 is 1fr, so bodies stretch and every button lands on the same baseline.
Tab 2

Forms — The Widest Label Sizes Every Row

Each field group is its own component with its own markup — yet all labels share one max-content column, sized by the longest label in the entire form. No fixed widths, no guessing.

.form {
  display: grid;
  grid-template-columns: max-content 1fr; ← widest label wins
  gap: 10px 14px;
}
.form-group {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
}

Named Lines Pass Through — Full-Bleed Layout

The parent names its lines (full, content). Every subgridded section places children by those names — body text in the content column, figures breaking to full width, all snapped to the same shared lines.

Article text sits in the named content column. This section is a subgrid — it inherits the parent's named lines without redefining anything.

figure — grid-column: full (breaks out, same shared lines)

Back to the content column. Every element in every section aligns to one outer grid — the full-bleed pattern with zero width calculations.

.article {
  grid-template-columns:
    [full-start] 1fr [content-start] minmax(0, 3fr) [content-end] 1fr [full-end];
}
.section { grid-column: full; grid-template-columns: subgrid; }
.section p     { grid-column: content; } ← names inherited!
.section figure { grid-column: full; }
Tab 3

The Gotchas

Four sharp edges plus the "subgrid does nothing" checklist.

⚠️ Gotcha 1: No Implicit Tracks
Child 1 — has a track
Child 2 — has a track
Child 3 — has a track
/* Subgrid axis = NO implicit tracks.
   Extra children pile into the LAST track. */
.card { grid-row: span 3; grid-template-rows: subgrid; }
/* 4 children, 3 tracks → quiet breakage */

A normal grid invents implicit tracks for overflow children. A subgridded axis doesn't — extra children get crammed into the final track. Content count must match spanned track count.

⚠️ Gotcha 2: Padding Eats Your Tracks
/* ❌ Padding/margin/border on the subgrid
   is SUBTRACTED from the edge tracks —
   shifting them out of parent alignment */
.card {
  grid-template-rows: subgrid;
  padding: 16px; ← misaligns edge tracks
}

/* ✅ Pad the ITEMS, not the subgrid */
.card > * { padding-inline: 16px; }

The subgrid's box spacing comes out of its first and last inherited tracks — visually breaking the alignment you adopted subgrid for. Keep the subgrid container spacing-free; pad children instead.

↔️ Gap: Inherited, Overridable
subgrid gap: 16px (tracks stay aligned)
.sub { grid-template-columns: subgrid; gap: 4px; }
/* Track LINES stay put — gap carves spacing
   out of the tracks, alignment preserved */

Gap inherits by default; overriding it changes spacing inside the subgrid without moving the inherited track lines. Edge-case tip: set it explicitly — legacy browser inconsistencies existed around inheritance.

🔍 "Subgrid Does Nothing" Checklist
1. Is the parent display: grid?

2. Is the subgrid element itself
   display: grid? (subgrid is a VALUE,
   not a display type)

3. Does it SPAN enough tracks?
   grid-row: span 4 / grid-column: 1 / -1

4. Subgrid on the right AXIS?
   (rows for card alignment, columns for forms)

5. Firefox DevTools grid inspector —
   draws every line, best-in-class

A subgrid only has as many tracks as it spans — span 1 inherits one track and looks like nothing happened. #3 is the most common miss.

🛡️ Fallback Pattern
/* Base: flexbox column — fine everywhere */
.card { display: flex; flex-direction: column; }
.card .btn { margin-top: auto; } ← button to bottom

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

Flex-with-margin-auto gets buttons to card bottoms everywhere (just not cross-card aligned); subgrid perfects it where supported. ~90% support in 2026 means most audiences skip the fallback entirely.

📐 Both Axes + Nesting Limits
/* Lock onto BOTH axes */
.widget {
  grid-template-columns: subgrid;
  grid-template-rows: subgrid;
  grid-area: 2 / 2 / 4 / 7;
}

/* Mix: subgrid columns + implicit rows */
.widget {
  grid-template-columns: subgrid;
  grid-auto-rows: min-content; ← rows free
}

Subgrid per-axis: lock columns, keep rows independent (or vice versa). Practical limit: keep subgrid nesting ≤2 levels — deeper chains have cross-browser edge cases and are hard to reason about.

Browser support: Baseline 2023 — Firefox 71+ (shipped first, Dec 2019!), Safari 16+, Chrome/Edge 117+ (Sept 2023). Roughly 90% global in 2026. Debug tip: Firefox's grid inspector visualizes subgrid lines and implicit tracks better than any other browser — diagnose alignment there.