The genuinely new addition to CSS Grid in recent years is subgrid (Baseline September 2023). It solves a problem CSS could not previously express: aligning the heading and body of cards across a whole row.
This article was published in 2024 and revised in September 2026. The subgrid code originally shown does not work, because the element did not span multiple tracks of the parent grid. It has been corrected, with the patterns actually used in production added.
subgridonly means something when the element spans multiple tracks of its parent. A declaration such asgrid-column: span 2is requiredgrid-template-areasandminmax()date from Grid’s original release around 2017—they are not new
Sponsored
Grid versus Flexbox
The short version: Grid when you need to align in two directions, Flexbox when you are laying things out in one.
| CSS Grid | Flexbox | |
|---|---|---|
| Dimensions | Rows and columns together | One axis (row or column) |
| Sizing | The parent defines the tracks | Content determines the size |
| Best for | Page skeletons, card grids, tabular layout | Navigation, button rows, centring |
| Visual definition | grid-template-areas |
None |
It is not about which is better—it is whether you have two directions to align.
subgrid: aligning nested elements to the parent’s tracks
This closed Grid’s biggest gap. Grid could only align its direct children; anything inside them formed an unrelated grid.
The problem
In a card grid, a title that wraps to two lines pushes that card’s body text out of line with the others.
<ul class="cards">
<li class="card">
<h3>Short title</h3>
<p>Body...</p>
<a href="#">Read more</a>
</li>
<li class="card">
<h3>A noticeably longer title that wraps onto two lines</h3>
<p>Body...</p>
<a href="#">Read more</a>
</li>
</ul>
The subgrid solution
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
/* define the three rows on the parent */
grid-template-rows: auto 1fr auto;
gap: 24px;
}
.card {
display: grid;
/* span the parent's three rows — without this, subgrid does nothing */
grid-row: span 3;
grid-template-rows: subgrid;
gap: 8px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 12px;
}
Now the body text and the link line up across every card, because the row heights are shared along the row.
The mistake to avoid
This does not work—it is what the article originally showed.
/* .child occupies one column, so there are no tracks to inherit */
.parent {
display: grid;
grid-template-columns: 1fr 2fr;
}
.child {
display: grid;
grid-template-columns: subgrid;
}
/* make it span all the parent's columns */
.parent {
display: grid;
grid-template-columns: 1fr 2fr;
}
.child {
grid-column: 1 / -1; /* ← required */
display: grid;
grid-template-columns: subgrid;
}
The rule is simply: if it does not span, there is nothing to inherit.
gap and line names
gapis inherited from the parent. Override it withrow-gap/column-gapon the child if needed- Line names defined on the parent are usable from the child, and the child can add its own
- Using
subgridon both axes disables implicit track creation. Excess children get crammed into the last track, so considergrid-auto-rowsfor the block axis
Supported in Firefox 71, Safari 16 and Chrome/Edge 117, reaching Baseline in September 2023.
Sponsored
The responsive pattern you will use most
A card grid with no media queries at all.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
}
The column count adjusts to the available width. No breakpoints required.
But it has a flaw
It overflows when the container is narrower than 260px, because the minimum in minmax() means “never smaller than this”.
.cards {
display: grid;
/* fall back to 100% when the container is narrow */
grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
gap: 24px;
}
min(260px, 100%) prevents horizontal scrolling on small screens. That one addition changes how the page behaves on phones.
auto-fit versus auto-fill
auto-fit |
auto-fill |
|
|---|---|---|
| With few items | Collapses empty tracks; items stretch | Keeps empty tracks; items stay their size |
| Use when | You always want to fill the width | You want alignment across rows |
If a single item should not balloon to full width, use auto-fill. Otherwise auto-fit is usually right.
grid-template-areas: drawing the layout
.layout {
display: grid;
grid-template-areas:
"header header"
"main sidebar"
"footer footer";
grid-template-columns: 1fr 280px;
grid-template-rows: auto 1fr auto;
min-height: 100dvh;
gap: 24px;
}
.layout > header { grid-area: header; }
.layout > main { grid-area: main; }
.layout > aside { grid-area: sidebar; }
.layout > footer { grid-area: footer; }
@media (width < 768px) {
.layout {
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
grid-template-columns: 1fr;
}
}
Rearranging the area names changes the layout entirely—the HTML is untouched.
One caution: when the visual order diverges from DOM order, keyboard navigation order breaks. Moving a sidebar below the content as above is fine; wholesale reordering is not.
Sponsored
Useful smaller techniques
Overlapping elements
Placing several elements in the same cell stacks them—no position: absolute needed.
.hero {
display: grid;
}
.hero > * {
grid-area: 1 / 1; /* everything in row 1, column 1 */
}
.hero__text {
place-self: center;
z-index: 1;
}
The parent’s height follows the image, which is easier to manage than position: relative plus explicit heights.
Spanning the full width
.full-bleed {
grid-column: 1 / -1; /* -1 is the last line */
}
Sizing implicit rows
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(120px, auto); /* height of generated rows */
}
With a variable number of items, omitting this gives you rows of inconsistent height.
Combining with container queries
A Grid component needs a different column count depending on where it sits. Branch on the parent’s width rather than the viewport’s.
.card-area {
container-type: inline-size;
}
.card {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
@container (width >= 400px) {
.card {
grid-template-columns: 120px 1fr;
}
}
The same card is now stacked in a sidebar and side by side in the main column. More on this in CSS @media: the features that actually landed.
Debugging
Grid has dedicated developer tools. Guessing at numbers is the slow way.
- Chrome / Edge: click the “grid” badge on an element to overlay track numbers and gaps
- Firefox: the Layout panel’s Grid section overlays area names and line numbers. It is the better tool for debugging
grid-template-areas
When something lands in the wrong place, visualise the track numbers first. It is almost always a miscounted line—lines start at 1, so three columns have four lines.
Summary
subgridworks only when the element spans parent tracks.grid-row: span 3or similar is required- Its best use is aligning headings and body text across a row of cards
gapis inherited from the parent; line names are available too- The responsive pattern is
repeat(auto-fit, minmax(min(260px, 100%), 1fr)). Withoutmin()it overflows on narrow screens auto-fitcollapses empty tracks;auto-fillkeeps them- Overlap by giving every child
grid-area: 1 / 1 - Set
grid-auto-rowswhen the item count varies - Component-level branching belongs to
@container grid-template-areasandminmax()are from 2017
For spacing, see adding space in flexbox and grid with the gap property.