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 subgrid with 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 subgrid must be a grid container (it needs display: 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;
}
Random

Alpine Mug

Good for coffee. Great for pretending you hike.

$12
Random

Desk Plant

Needs water. Unlike your code reviews.

$18
Random

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 / -1 makes 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: subgrid reuses 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 gap on 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;
}
Random

Parent gap changes spacing between parent tracks.

Card gap is separate (still 10px inside the card).

$24
Random

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 (or display: inline-grid) on the element that says grid-template-columns: subgrid or grid-template-rows: subgrid.
  • If it’s not a grid container, subgrid has 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 / -1 or grid-column: span 3.

5) Are you accidentally overriding it?

  • Make sure you don’t have another rule later that sets grid-template-columns back 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: subgrid and 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 gap on 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 / -1 and/or grid-row: span N.
  • Set gap on 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.