Articles /
Subgrid and layout
The CSS feature that finally makes card grids align without fixed heights or JavaScript.
The card alignment problem
You have three cards in a row. Card one has a short title. Card two has a long title that wraps to two lines. Card three is in between. Without subgrid, the only way to make their body text start at the same vertical position is: fixed min-height on the title (brittle), JavaScript to equalize heights (script-dependent), or display: flex; flex-direction: column with margin-top: auto on the footer (workaround).
Subgrid is the structural solution. Cards share the parent's row tracks, so alignment is a property of the grid, not a hack.
How subgrid works
/* Parent: define column + row tracks */
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* 4 row tracks: eyebrow | title | body | footer */
grid-template-rows: auto auto 1fr auto;
gap: 1rem;
}
/* Card: opt into the parent's row tracks */
.card {
display: grid;
grid-template-rows: subgrid; /* ← inherit parent's rows */
grid-row: span 4; /* span all 4 tracks */
/* Internal spacing */
padding: 1rem;
gap: 0; /* no extra gap — padding handles spacing */
}
/* Card children automatically align to parent tracks */
.card .eyebrow { /* occupies row 1 across all cards */ }
.card h2 { /* occupies row 2 — titles align */ }
.card p { /* occupies row 3 — 1fr, stretches */ }
.card footer { /* occupies row 4 — always bottom */ }
The 1fr body row is the key: it stretches to fill remaining space, so all footers land at the same position regardless of how much body text is in each card.
Column subgrid
Subgrid works on columns too — useful for aligning labels and inputs in a form, or columns in a definition list.
/* 2-column definition list with aligned term + definition */
.dl-grid {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0.5rem 1.5rem;
align-items: baseline;
}
/* Each dt/dd pair participates in the 2-column grid */
/* No subgrid needed here — they're direct children */
/* Subgrid on columns in a card */
.card-columns {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1; /* span all parent columns */
}
Named grid lines with subgrid
/* Parent can use named lines */
.card-grid {
grid-template-rows:
[eyebrow-start] auto [eyebrow-end title-start]
auto [title-end body-start]
1fr [body-end footer-start]
auto [footer-end];
}
/* Child references named lines through subgrid */
.card-footer {
grid-row: footer-start / footer-end;
}
Combining with container queries
Container queries control how many columns the grid has; subgrid controls how children align within those columns. Together they give you a fully adaptive, structurally aligned card grid.
.wrapper {
container-type: inline-size;
container-name: cards;
}
.card-grid {
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: 1rem;
}
/* Default: 1 column, no subgrid needed */
@container cards (min-width: 32rem) {
.card-grid { grid-template-columns: repeat(2, 1fr); }
.card { display: grid; grid-template-rows: subgrid; grid-row: span 4; }
}
@container cards (min-width: 48rem) {
.card-grid { grid-template-columns: repeat(3, 1fr); }
}