webdev.complete
📐 CSS Grid
🎨CSS Power
Lesson 16 of 117
20 min

Subgrid & Masonry

When you need nested grids to line up, and the masonry future.

Once you can spin up a Grid in your sleep, you start running into problems that need the deeper toolkit: nested grids that need to align with their parent, Pinterest-style masonry, weirdly shaped overflow, and the never-ending question of "where did this extra row come from?" This lesson covers the advanced features that turn Grid from useful into magical.

Explicit vs implicit grid

Everything you write in grid-template-columns and grid-template-rows is the explicit grid. The moment you place an item beyond those tracks, the browser creates the implicit grid. Its tracks are sized by grid-auto-columns and grid-auto-rows, which default to auto(meaning "fit content").

css
.list {
  display: grid;
  grid-template-columns: 1fr 1fr;       /* explicit: two columns */
  grid-auto-rows: 100px;                /* implicit rows are all 100px */
  gap: 8px;
}

Drop fifty children into that container and you get twenty-five rows, each 100px tall. You never declared the rows. Grid created them because items needed cells.

Dense packing fills the holes

Normally Grid places items in source order. If item three is too wide to fit in row one, Grid leaves a hole and puts it in row two. The dense keyword on grid-auto-flow changes that: Grid will go back and fill earlier holes with later items that fit.

css
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: dense;   /* fill any earlier holes */
  gap: 12px;
}
Dense breaks reading order
grid-auto-flow: densecan scramble the visual order relative to the DOM order. Screen readers still announce in DOM order, so don't use it for content that depends on sequence (like a numbered list or a step-by-step tutorial). Great for image galleries, dangerous for articles.

Subgrid: nested alignment that actually aligns

Subgrid is one of the most-requested features in CSS history, and it finally landed in every evergreen browser by 2023. Without subgrid, a nested grid has its own track sizing that doesn't know about the parent. With subgrid, the child grid inheritsthe parent's tracks. Headers, body, and footers in a row of cards can line up perfectly even if each card has different content.

css
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto 1fr auto;  /* header / body / footer */
  gap: 16px;
}

.card {
  display: grid;
  grid-row: span 3;                   /* take all three rows */
  grid-template-rows: subgrid;        /* inherit parent's row sizing */
}

Now every card's header sits on the parent's first row, body on the second, footer on the third. If one card has a long title, every card's header grows together. No JavaScript, no equal-height hacks.

Masonry: like Pinterest, in pure CSS

Masonry layout (uneven rows, vertically packed columns) used to require a JavaScript library. CSS masonry is now shipping in browsers as grid-template-rows: masonry (and behind a flag in some). The exact syntax is still being standardized. As of 2026 you can use it like this:

css
.pinboard {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry;   /* let Grid pack vertically */
  gap: 12px;
}
Check support before you ship
Masonry support is still uneven across browsers in 2026. Safari has shipped it, Chrome is rolling it out, Firefox has it behind a flag. Use a feature query and fall back to a regular grid for older browsers:
css
@supports (grid-template-rows: masonry) {
  .pinboard { grid-template-rows: masonry; }
}

Subgrid in action

Three cards, each with a title, body, and footer. Each card has different content length, but everything lines up because we use subgrid. Try removing the subgrid line and watch the alignment fall apart.

<!doctype html>
<html>
<head><link rel="stylesheet" href="styles.css"></head>
<body>
  <section class="cards">
    <article class="card">
      <header><h3>Espresso</h3></header>
      <p>A small but mighty shot. Two ingredients, infinite arguments.</p>
      <footer><button>Order</button></footer>
    </article>

    <article class="card">
      <header><h3>Cold Brew Reserve from Single-Origin Ethiopia</h3></header>
      <p>Steeped for 18 hours. Notes of blueberry and citrus zest.</p>
      <footer><button>Order</button></footer>
    </article>

    <article class="card">
      <header><h3>Latte</h3></header>
      <p>Two-thirds steamed milk, one-third espresso. The crowd favorite, always reliable, the safest order on the menu.</p>
      <footer><button>Order</button></footer>
    </article>
  </section>
</body>
</html>

Naming areas with subgrid

Subgrid can inherit named lines from the parent. Combined with grid-template-areas, you get layouts where parents and children share a vocabulary, and you only define alignment in one place. This becomes essential in design systems where dozens of card variants all need to align identically.

Anti-pattern: 100% width children

New Grid users sometimes set width: 100% on grid children, which is redundant. Grid already gives the child the cell width. Adding width: 100% can cause overflow when the child has padding. Trust the cell.

Quiz

Quiz1 / 4

What does grid-auto-flow: dense do?

Recap

  • Explicit grid: what you declare. Implicit grid: what Grid generates when items spill past your tracks.
  • grid-auto-flow: dense backfills holes. Great for galleries, bad for content order.
  • Subgrid inherits track sizing from the parent so nested layouts align perfectly.
  • CSS masonry is shipping (with uneven support). Wrap it in @supports for safety.
  • Don't set width: 100% on grid children. The cell already does that.