HTML5 Please

CSS Subgrid: How It Works, Patterns Worth Using and Support in 2026

CSS Subgrid: How It Works, Patterns Worth Using and Support in 2026

You've built a row of cards, the titles wrap to different lengths, and now every button sits at a different height. Sound familiar? That's the exact problem subgrid was made for. It lets a nested element borrow the rows or columns of its parent grid instead of inventing its own, so things inside separate boxes finally line up with each other. Our verdict up front: use it. It's been in every major engine since September 2023, and on 15 March 2026 it became Baseline widely available.

What problem does subgrid solve?

A grid only lays out its direct children. The moment you put display: grid on a child, that child starts a brand new grid with tracks of its own, and it has no idea what its siblings are doing. Card one's title takes two lines, card two's takes one, and the content below drifts out of line.

Before subgrid, people patched this with fixed heights, min-height guesses, or a script that measured every card and stretched the shorter ones. All of them break as soon as the copy changes. Subgrid fixes it at the layout level: the nested grid says "use my parent's tracks", and the browser sizes each shared track by the tallest thing in it across all the cards.

The syntax in three lines

.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 24px; }
.card  { display: grid; grid-row: span 3; grid-template-rows: subgrid; gap: 12px; }

That's the whole trick. Two details make it work:

  • The span. A subgrid only gets the tracks its element covers. A card with a title, a text block and a button needs three rows, so it has to span three. Forget the span and you get one row with everything crammed into it.
  • The keyword. grid-template-rows: subgrid inherits the rows. You can do the same for columns, or for both. Whichever axis you don't subgrid behaves like a normal grid you define yourself.

What about the gap: 12px on the card? It's optional. A subgrid inherits the parent's gaps by default, and setting its own replaces them, which is usually what you want inside a card.

Pattern 1: cards that line up

This is the one most people come for. Give every card the same three parts and let the rows do the rest:

.card > h3     { grid-row: 1; }
.card > p      { grid-row: 2; }
.card > .cta   { grid-row: 3; align-self: end; }

Why place each part explicitly? Because real cards aren't always complete. If one card has no description, auto-placement would move its button up into row two and the alignment breaks again. Pinning each part to a row keeps an empty track where the missing piece would be. If you'd rather style the incomplete cards differently, :has() can tell you which ones they are: .card:not(:has(p)).

Pattern 2: forms where labels share one column

Ever wondered why labels in a long form never quite line up? Each field is usually wrapped in its own element, which used to mean every label got its own width:

form   { display: grid; grid-template-columns: max-content 1fr; gap: 12px 16px; }
.field { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }

Now the label column is as wide as the longest label in the whole form, and every input starts at the same x position. You keep the wrapper for error messages and spacing, and you don't need a table or fixed label widths.

Pattern 3: aligning nested content to the page grid

Lots of sites use a three-column page grid so text stays readable and images can break out to full width. The trouble starts when the content sits inside an <article> or a component, because that wrapper hides the page columns from its children. Subgrid passes them through, including the line names:

.page {
  display: grid;
  grid-template-columns: [full-start] 1fr [content-start] min(65ch, 100% - 32px) [content-end] 1fr [full-end];
}
.page > article { grid-column: full; display: grid; grid-template-columns: subgrid; }
article > *       { grid-column: content; }
article > figure  { grid-column: full; }

Paragraphs stay in the readable column and figures span the full width, even though both are children of the article and not of .page. Want images to break out without negative margins? This is the cleanest way we know of.

Things that trip people up

  • No extra tracks in the inherited direction. A subgrid can't grow rows or columns where it borrows them. A fourth child in a card that spans three rows doesn't get a fourth row; it lands in the existing tracks. Count your parts and span accordingly.
  • Inherited gaps. Does the spacing inside your cards suddenly match the spacing between them? That's the parent's gap coming through. Set gap on the subgrid to override it.
  • Padding and borders on the subgrid. They still apply, but they push the content of the first and last tracks inward rather than shifting the tracks themselves. Your lines stay aligned with the parent; the edge items just get extra breathing room.
  • Expecting it to fix auto-fill row counts. The parent still decides how many cards sit in a row. Subgrid aligns what's inside them; it doesn't change how many there are. For components that react to their own width, that's a job for container queries.
  • Using it where Flexbox was the right tool. One row of buttons doesn't need a shared grid. If you're unsure which layout model a component wants, our Grid vs Flexbox guide covers the call.

Browser support in 2026

EngineSubgrid sinceReleased
Firefox71December 2019
Safari (macOS and iOS)16September 2022
Chrome, Edge, Opera, Samsung InternetChrome 117September 2023

Firefox had it four years before anyone else, which is why older tutorials say "Firefox only". That hasn't been true since Chrome 117 shipped it in September 2023. According to the web-features data marks subgrid Baseline newly available from 15 September 2023 and widely available from 15 March 2026. On 29 October 2026 caniuse put global support at 94.43%.

So who's left out? Mostly iPhones stuck on iOS 15 or older, plus old Android WebViews. If your analytics show a real share of those, wrap the subgrid rules in a feature query and let the cards fall back to independent grids:

@supports (grid-template-rows: subgrid) {
  .card { grid-template-rows: subgrid; grid-row: span 3; }
}

The fallback isn't broken, it's just unaligned, which is exactly how these layouts looked before. That's what makes subgrid a safe progressive enhancement rather than a gamble.

Subgrid in Tailwind

Using Tailwind? It's had the utilities since v3.4: grid-rows-subgrid and grid-cols-subgrid, used together with a span such as row-span-3. The same rules apply. The card needs the span, and the parent needs grid with its own column template.

Our verdict

Use. Subgrid fixes a real alignment problem with two declarations, it's supported in every current engine, and the failure mode on old devices is a layout that's merely unaligned. Our grid readiness page covers the rest of the grid family, and the MDN subgrid guide is the reference worth keeping open while you try it.

FAQ

What is CSS subgrid?

It's a value for grid-template-rows and grid-template-columns that makes a nested grid use its parent's tracks instead of its own. Elements inside different children of the same grid can then line up with each other.

Is subgrid supported in all browsers?

Yes, in every current engine: Firefox since 71, Safari since 16 and Chrome and Edge since 117. It reached Baseline widely available on 15 March 2026, and caniuse put global support at 94.43% on 29 October 2026.

Does subgrid work in Safari on iPhone?

From iOS 16 onwards, yes. Devices on iOS 15 or older don't support it, so use an @supports block if those visitors matter to you.

Why isn't my subgrid working?

The usual cause is a missing span. The element with grid-template-rows: subgrid only inherits the rows it covers, so a card with three parts needs grid-row: span 3. The second most common cause is that the parent isn't a grid at all.

Can I use subgrid for rows and columns at the same time?

Yes. Set both grid-template-rows: subgrid and grid-template-columns: subgrid, and make sure the element spans the rows and columns it needs from the parent.