CSS Subgrid: Aligning Nested Grid Items to the Parent Grid
Subgrid is the solution to one of the most frustrating layout problems in CSS: when you have a grid of cards, and each card has multiple rows (image, title, body, button), but the content inside each card doesn't align across cards. Subgrid makes nested elements participate in the parent grid's track sizing.
1. The Problem Subgrid Solves
Without subgrid: cards with different content heights don't align
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Image │ │ Image │ │ Image │
├──────────┤ ├──────────┤ ├──────────┤
│Short │ │A much │ │Medium │
│Title │ │longer │ │Title │
│ │ │title here│ │ │
├──────────┤ ├──────────┤ ├──────────┤
│Short desc│ │Very long │ │Desc text │
│ │ │desc that │ │here │
│ │ │wraps more│ │ │
├──────────┤ ├──────────┤ ├──────────┤
│[Button] │ │[Button] │ │[Button] │
└──────────┘ └──────────┘ └──────────┘
With subgrid: all zones align across cards
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Image │ │ Image │ │ Image │ ← row 1: same height
├──────────┤ ├──────────┤ ├──────────┤
│Short │ │A much │ │Medium │ ← row 2: tallest wins,
│Title │ │longer │ │Title │ all align
│ │ │title here│ │ │
├──────────┤ ├──────────┤ ├──────────┤
│Short desc│ │Very long │ │Desc text │ ← row 3: tallest wins
│ │ │desc that │ │here │
│ │ │wraps more│ │ │
├──────────┤ ├──────────┤ ├──────────┤
│[Button] │ │[Button] │ │[Button] │ ← row 4: always aligned
└──────────┘ └──────────┘ └──────────┘
2. The Core Syntax
/* PARENT: Define the grid */
.o-card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto; /* Rows are implicit — subgrid will handle */
}
/* CHILD (each card): Span parent columns AND inherit parent row tracks */
.c-card {
grid-column: span 1; /* Each card spans 1 column */
display: grid;
grid-template-rows: subgrid; /* ← THE KEY: use parent's row sizing */
grid-row: span 4; /* Span 4 rows (image, title, body, button) */
}
/* GRANDCHILDREN: Sit in the inherited tracks automatically */
.c-card__image { /* Row 1 of parent grid */ }
.c-card__title { /* Row 2 of parent grid */ }
.c-card__body { /* Row 3 of parent grid */ }
.c-card__btn { /* Row 4 of parent grid */ }
Full Working Example
<div class="o-card-grid">
<article class="c-card">
<img class="c-card__image" src="img1.jpg" alt="">
<h2 class="c-card__title">Short Title</h2>
<p class="c-card__body">Brief description.</p>
<a class="c-card__btn c-btn c-btn--outline" href="#">Learn More</a>
</article>
<article class="c-card">
<img class="c-card__image" src="img2.jpg" alt="">
<h2 class="c-card__title">A Much Longer Card Title That Wraps</h2>
<p class="c-card__body">This card has substantially more body text that wraps across multiple lines.</p>
<a class="c-card__btn c-btn c-btn--outline" href="#">Learn More</a>
</article>
<article class="c-card">
<img class="c-card__image" src="img3.jpg" alt="">
<h2 class="c-card__title">Medium Title</h2>
<p class="c-card__body">Medium amount of text here.</p>
<a class="c-card__btn c-btn c-btn--outline" href="#">Learn More</a>
</article>
</div>
/* The complete solution */
.o-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-auto-rows: auto;
gap: 1.5rem;
align-items: start;
}
.c-card {
display: grid;
grid-template-rows: subgrid; /* Inherit row sizing from parent */
grid-row: span 4; /* Must declare how many rows this card uses */
background: var(--color-surface);
border-radius: var(--radius-lg);
border: 1px solid var(--color-border);
overflow: hidden;
}
.c-card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.c-card__title {
padding: 1.5rem 1.5rem 0;
font-size: var(--text-lg);
font-weight: 700;
align-self: start; /* Don't stretch — sit at top of row */
}
.c-card__body {
padding: 0.75rem 1.5rem 0;
color: var(--color-text-muted);
line-height: 1.7;
align-self: start;
}
.c-card__btn {
padding: 0.75rem 1.5rem 1.5rem;
align-self: end; /* Push button to bottom of its row */
}
3. Subgrid on Columns
You can also use subgrid to inherit column tracks:
/* Parent defines columns */
.o-layout {
display: grid;
grid-template-columns: 1fr 3fr 1fr; /* sidebar | content | aside */
gap: 2rem;
}
/* Child uses parent's columns for internal layout */
.c-article-card {
grid-column: 1 / -1; /* Spans all 3 columns */
display: grid;
grid-template-columns: subgrid; /* Inherits the 1fr 3fr 1fr definition */
}
.c-article-card__meta { grid-column: 1; } /* First column = sidebar track */
.c-article-card__content { grid-column: 2; } /* Second column = content track */
.c-article-card__aside { grid-column: 3; } /* Third column = aside track */
/* All cards now align to the SAME column widths as the parent grid */
4. Subgrid on Both Axes
/* Inherit both rows and columns */
.c-card {
display: grid;
grid-column: span 2;
grid-row: span 3;
grid-template-columns: subgrid; /* Inherit parent column tracks */
grid-template-rows: subgrid; /* Inherit parent row tracks */
}
5. Named Lines with Subgrid
Subgrid inherits named grid lines from the parent:
/* Parent with named lines */
.o-page-layout {
display: grid;
grid-template-columns:
[full-start]
[content-start sidebar-start] 1fr
[sidebar-end main-start] 3fr
[main-end content-end]
[full-end];
}
/* Child using inherited named lines */
.c-component {
grid-column: full-start / full-end; /* Span the full named area */
display: grid;
grid-template-columns: subgrid; /* Inherits all named lines */
}
.c-component__sidebar {
grid-column: sidebar-start / sidebar-end; /* Uses parent's named lines */
}
.c-component__main {
grid-column: main-start / main-end;
}
6. The Old Workaround vs Subgrid
Before subgrid, the card alignment problem required JavaScript or hacky CSS:
/* ❌ Old hack: fixed height rows — breaks with dynamic content */
.c-card__title {
min-height: 4rem; /* Magic number — breaks when title is longer */
}
/* ❌ Old hack: JavaScript equalHeight */
/* Requires JS resize observer + manual height calculation */
/* ❌ Old hack: Flexbox column with flex: 1 on body */
.c-card { display: flex; flex-direction: column; }
.c-card__body { flex: 1; }
/* Works for pushing footer to bottom, but titles STILL don't align across cards */
/* ✅ Subgrid: no hacks, no JS, pure CSS */
.c-card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
}
7. Browser Support
| Browser | Support | Version |
|---|---|---|
| Chrome | ✅ Full | 117+ |
| Firefox | ✅ Full | 71+ |
| Safari | ✅ Full | 16+ |
| Edge | ✅ Full | 117+ |
| Global coverage | ~96% |
Subgrid is safe to use in production today with no fallback needed for most projects.
/* Optional: @supports fallback for very old browsers */
@supports (grid-template-rows: subgrid) {
.c-card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
}
}
/* Without @supports: fallback uses flexbox approach */
.c-card {
display: flex;
flex-direction: column;
}
.c-card__body { flex: 1; } /* At least pushes button down */
8. Common Subgrid Mistakes
/* ❌ Mistake 1: Forgetting grid-row: span N on the child */
.c-card {
display: grid;
grid-template-rows: subgrid;
/* Missing: grid-row: span 4 */
/* Card only spans 1 row — subgrid has nothing to inherit */
}
/* ❌ Mistake 2: Parent rows not defined */
.o-card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Missing: grid-auto-rows or grid-template-rows */
/* Implicit rows are auto-sized per row — subgrid inherits from implicit rows */
/* This still works but all rows are independent — not what you want */
}
/* Fix: use grid-auto-rows: auto (default) is fine — subgrid shares implicit rows */
/* ❌ Mistake 3: Trying to use subgrid without parent grid */
.c-card {
display: grid;
grid-template-rows: subgrid; /* Parent is display:block — no grid tracks to inherit */
/* Result: browser treats as 'none' — behaves like grid-template-rows: none */
}
9. AI Prompt for Subgrid Cards
Build a responsive card grid using CSS Subgrid for cross-card alignment.
Requirements:
- 3 columns on desktop, 2 on tablet, 1 on mobile (auto-fill, minmax)
- Each card has: image (16:9), title, body text, CTA button
- Cards share row tracks so: all titles align, all buttons align at bottom
- Use grid-template-rows: subgrid on each card (grid-row: span 4)
- BEM naming: .o-card-grid / .c-card / .c-card__image etc.
- Tokens: [PASTE :root]
- @supports fallback for browsers without subgrid
Output: HTML structure for 3 sample cards + complete CSS