What is CSS subgrid?
Subgrid lets a nested grid (a grid inside a grid item) reuse the parent grid’s track sizing. In plain words: your inner layout can align perfectly with the outer layout.
Without subgrid, a nested grid has to define its own columns/rows. That usually means your inner columns will be “close, but not quite” aligned with the parent.
Subgrid is especially useful for:
- Card lists where titles, prices, and buttons should line up across all cards
- Editorial layouts where captions align to the same columns as the main content
- Forms where labels/inputs align across sections
- Reusable components that must “snap to” the page grid
CSS Subgrid support
As of writing, CSS Subgrid has alright browser coverage: ~87.8% global usage supports it.
To check the latest data, keep this page bookmarked: Can I use: CSS Subgrid.
Practical advice for real projects:
- Treat subgrid as progressive enhancement. Build a solid “works everywhere” layout first (normal nested grid, flex, or simple stacking).
- Layer subgrid on top to improve alignment and polish in supporting browsers.
- Test your fallback by temporarily disabling subgrid in DevTools (or by replacing
subgridwith a basic template) to ensure the layout still reads well.
Grid refresher: the minimum you need
CSS Grid has two main ideas:
- Tracks: the rows and columns you define (like
grid-template-columns) - Lines: the boundaries between tracks (where items can start/end)
A parent becomes a grid container when you do display: grid. Its direct children become grid items and
can be placed into tracks.
Now the important part: a grid item can also become a grid container. That’s how you get a “grid inside a grid”. But unless you use subgrid, that nested grid is totally independent.
.layout {
display: grid;
grid-template-columns: 140px 1fr 140px;
gap: 16px;
}
.sidebar,
.content,
.aside {
border: 2px solid #111;
padding: 12px;
}
.layout {
display: grid;
grid-template-columns: 220px 1fr 220px;
gap: 16px;
}
.sidebar,
.content,
.aside {
border: 2px solid #111;
padding: 12px;
}
.layout {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
gap: 16px;
}
.sidebar,
.content,
.aside {
border: 2px solid #111;
padding: 12px;
}
*,
::before,
::after {
box-sizing: border-box;
}
body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
.layout {
background: #f6f6f6;
padding: 14px;
border: 2px dashed #111;
border-radius: 12px;
}
.sidebar {
background: #fff;
}
.content {
background: #fff;
}
.aside {
background: #fff;
}
h3 {
margin: 0 0 8px;
font-size: 16px;
}
p {
margin: 0;
line-height: 1.4;
font-size: 14px;
}
Main content
This is the center column.
Aside
Extra info
If you aren't yet familiar with CSS Grid, consider starting with the CSS Grid Interactive Tutorial.
The subgrid mental model
Subgrid is not a “new type of grid”. It’s more like saying: “Hey nested grid, don’t invent your own tracks…” “…use the parent’s tracks for this axis.”
You can subgrid:
- Columns:
grid-template-columns: subgrid; - Rows:
grid-template-rows: subgrid; - Both:
grid-template: subgrid / subgrid;
Key rules to remember:
- The element using
subgridmust be a grid container (it needsdisplay: grid). - It must be inside a grid (its parent or ancestor layout must create the tracks it will inherit).
- Subgrid inherits the track sizing from the parent for that axis, but you still place items inside the subgrid normally.
- You typically pair subgrid with spans so the nested grid covers multiple parent tracks (otherwise it only “inherits” a tiny slice).
Your first subgrid: aligning nested columns
Let’s build a common layout: a list of “cards”, where each card has a mini layout inside it (image + text + price). Without subgrid, each card’s inner columns won’t necessarily line up with the page grid.
We’ll do it in two steps:
- Nested grid (no subgrid): each card defines its own columns (Snippet 1 and 2)
- Subgrid: each card inherits the parent columns so everything lines up (Snippet 3)
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
}
.card {
display: grid;
grid-template-columns: 40px 1fr;
gap: 12px;
}
.card__media {
aspect-ratio: 1 / 1;
}
.grid {
display: grid;
grid-template-columns: 140px 1fr 140px;
gap: 16px;
}
.card {
display: grid;
grid-template-columns: 40px 1fr;
gap: 12px;
}
.card__media {
aspect-ratio: 1 / 1;
}
.grid {
display: grid;
grid-template-columns: 90px 1fr 120px;
gap: 16px;
}
.card {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
gap: 12px;
}
.card__media {
aspect-ratio: 1 / 1;
}
*,
::before,
::after {
box-sizing: border-box;
}
body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
.grid {
padding: 14px;
border: 2px dashed #111;
background: #f6f6f6;
border-radius: 12px;
}
.card {
background: #fff;
border: 2px solid #111;
border-radius: 12px;
padding: 12px;
align-items: center;
}
.card__media img {
width: 100%;
height: 100%;
display: block;
border-radius: 10px;
object-fit: cover;
border: 2px solid #111;
}
.card__title {
margin: 0;
font-size: 14px;
line-height: 1.25;
}
.card__desc {
margin: 6px 0 0;
font-size: 13px;
line-height: 1.35;
opacity: 0.9;
}
.price {
justify-self: end;
font-weight: 700;
font-size: 14px;
padding: 8px 10px;
border: 2px solid #111;
border-radius: 999px;
background: #fff;
}
.stack {
display: grid;
gap: 6px;
}
![]()
Alpine Mug
Good for coffee. Great for pretending you hike.
$12![]()
Desk Plant
Needs water. Unlike your code reviews.
$18![]()
Notebook
For ideas, sketches, and “todo: fix layout”.
$9
What to notice:
- In the nested grid snippets, the card uses
grid-template-columns: 40px 1fr. That’s independent from the parent. - In the subgrid snippet (3), the card uses
grid-template-columns: subgrid, so it inherits the parent columns. grid-column: 1 / -1makes each card span the full parent grid width (so the subgrid has access to all the parent columns).
With snippet 3 active, try editing the grid-template-columns values on the parent grid.
If you only remember one thing: subgrid makes the nested grid “snap to” the outer tracks.
Subgrid rows: aligning content across cards
Subgrid isn’t just for columns. It can be even more magical for rows, especially when card content length varies.
Goal: make all cards share the same row structure so the “button row” lines up across the whole list.
.list {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto 1fr;
gap: 14px;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
gap: 10px;
}
.card__actions {
align-self: end;
}
*,
::before,
::after {
box-sizing: border-box;
}
body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
.list {
padding: 14px;
border: 2px dashed #111;
background: #f6f6f6;
border-radius: 12px;
}
.card {
background: #fff;
border: 2px solid #111;
border-radius: 12px;
padding: 12px;
}
.card h3 {
margin: 0;
font-size: 15px;
}
.card p {
margin: 0;
font-size: 13px;
line-height: 1.35;
opacity: 0.9;
}
.card__actions {
display: flex;
gap: 8px;
}
.button {
border: 2px solid #111;
border-radius: 10px;
padding: 8px 10px;
background: #fff;
font-weight: 700;
font-size: 13px;
}
Short title
Short description.
Medium title that wraps a bit
This description is longer, which usually makes action buttons jump around.
Long title that wraps a lot because it is feeling very chatty today
Very long description. We want the bottom actions row to align across the whole grid, even with different content lengths.
Why this works:
- The parent grid defines rows:
grid-template-rows: auto auto 1fr. - Each card spans 3 parent rows:
grid-row: span 3. - Inside the card,
grid-template-rows: subgridreuses those parent rows, so the “actions” row lines up.
Subgrid on both axes
You can subgrid both columns and rows when you want a nested component to fully “lock onto” the outer grid.
This is great when the page grid is the source of truth and components are “grid-aware”.
.page {
display: grid;
grid-template-columns: 120px 1fr 140px;
grid-template-rows: auto auto;
gap: 14px;
}
.panel {
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
grid-column: 1 / -1;
grid-row: 1 / -1;
gap: 10px;
}
.page {
display: grid;
grid-template-columns: 180px 1fr 200px;
grid-template-rows: auto auto;
gap: 14px;
}
.panel {
display: grid;
grid-template: subgrid / subgrid;
grid-column: 1 / -1;
grid-row: 1 / -1;
gap: 10px;
}
*,
::before,
::after {
box-sizing: border-box;
}
body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
.page {
padding: 14px;
border: 2px dashed #111;
background: #f6f6f6;
border-radius: 12px;
}
.box {
border: 2px solid #111;
background: #fff;
border-radius: 12px;
padding: 12px;
font-size: 14px;
}
.box p {
margin: 0;
line-height: 1.35;
}
.tag {
display: inline-block;
padding: 4px 8px;
border: 2px solid #111;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
background: #fff;
}
Col 1 This area snaps to the first column.
Col 2 This snaps to the middle column.
Col 3 This snaps to the last column.
Row 2 Second row inside the same shared row tracks.
Row 2 Second row inside the same shared row tracks.
Row 2 Second row inside the same shared row tracks.
A small but important note: in a subgrid container, you still place items normally (using auto-placement, or explicit placement). The difference is just where the tracks come from.
Gap and sizing with subgrid
Two common “wait, what?” moments:
- Track sizing comes from the parent for the subgridded axis.
- Gap is not automatically inherited. You set
gapon the subgrid container if you want spacing between its children.
Let’s make that visual with a slider that changes the parent gap. You’ll see how the parent spacing
changes, and you can keep the subgrid’s internal spacing separate.
.wrapper {
display: grid;
grid-template-columns: 110px 1fr 140px;
gap: 16px;
}
.card {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
gap: 10px;
}
*,
::before,
::after {
box-sizing: border-box;
}
body {
font-family: ui-sans-serif, system-ui, sans-serif;
}
.wrapper {
padding: 14px;
border: 2px dashed #111;
background: #f6f6f6;
border-radius: 12px;
}
.card {
border: 2px solid #111;
background: #fff;
border-radius: 12px;
padding: 12px;
align-items: center;
}
.media {
aspect-ratio: 1 / 1;
}
.media img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
border-radius: 10px;
border: 2px solid #111;
}
.text {
display: grid;
gap: 6px;
}
.text p {
margin: 0;
font-size: 13px;
line-height: 1.35;
}
.pill {
justify-self: end;
border: 2px solid #111;
border-radius: 999px;
padding: 8px 10px;
font-weight: 700;
font-size: 13px;
background: #fff;
}
![]()
Parent gap changes spacing between parent tracks.
Card gap is separate (still 10px inside the card).
$24![]()
This card also uses the same subgrid columns.
Everything stays aligned as the outer gap changes.
$31
CSS subgrid not working: debugging checklist
If subgrid “does nothing”, it’s almost always one of these.
1) Is the element with subgrid actually a grid container?
- You need
display: grid(ordisplay: inline-grid) on the element that saysgrid-template-columns: subgridorgrid-template-rows: subgrid. - If it’s not a grid container,
subgridhas nothing to apply to.
2) Is there a parent grid to inherit from?
- Subgrid inherits tracks from an ancestor grid.
- If the parent layout is not a grid (or the element isn’t inside the grid you think it is), subgrid can’t inherit tracks.
3) Are you subgridding the correct axis?
- To inherit columns:
grid-template-columns: subgrid - To inherit rows:
grid-template-rows: subgrid - It’s common to set the wrong one and then wonder why nothing changed.
4) Does the subgrid span enough tracks?
- If your subgrid container only occupies one parent column, it will only inherit one column track. That can look like “nothing happened”.
- Try making it span more tracks, for example:
grid-column: 1 / -1orgrid-column: span 3.
5) Are you accidentally overriding it?
- Make sure you don’t have another rule later that sets
grid-template-columnsback to something else. - In DevTools, inspect the computed styles for
grid-template-columns/grid-template-rows.
6) Browser support (and “it works on my machine”)
- If you test in a browser that doesn’t support subgrid, it will usually ignore the value and fall back to something else.
- Quick test: set
grid-template-columns: subgridand see if DevTools shows it as valid or crossed out.
7) Auto-placement confusion (items aren’t where you expect)
- Even with subgrid, your items still need to be placed (explicitly or via auto-flow).
- If items stack strangely, add explicit placement temporarily like
grid-column: 1,grid-column: 2, etc., to confirm the tracks are correct.
8) Remember: gap is not track sizing
- Subgrid inherits the track sizing, not your spacing preferences.
- If it “looks wrong”, you might just need to set
gapon the subgrid container.
Recap: CSS subgrid cheat sheet
- Use subgrid when a nested layout should align to the parent grid tracks.
- Columns:
grid-template-columns: subgrid - Rows:
grid-template-rows: subgrid - Both:
grid-template: subgrid / subgrid - Make the subgrid container span enough tracks:
grid-column: 1 / -1and/orgrid-row: span N. - Set
gapon the subgrid container if you want spacing between its children.
If grid is the stage, subgrid is the choreographer that makes all your components hit their marks.
