EduFlow
Modern CSS Mastery
0%

Section 2 · Layout That Holds Up

Subgrid for Aligned Cards

17 min3 min readCode playground

Lesson at a glance

Use subgrid so the time, title, abstract and room of every session card line up across a row of cards, whatever the length of each title.

  1. 1Why nested grids don’t line up
  2. 2Borrowing the parent’s rows
  3. 3Gaps, lines and columns
  4. 4Your turn

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Put three Waypoint session cards side by side and the problem jumps out. One title fits on a line, one wraps to two, one to three. Each card lays out its own content, so the abstracts start at three different heights and the room labels at the bottom form a staircase. Designers notice. Users notice less, but the page looks unfinished.

The old fixes were fixed heights (which break with real content) or JavaScript that measures every title. Subgrid makes the cards share one set of rows.

Why nested grids don’t line up

When a card becomes display: grid, it creates its own, independent tracks. Its title row is as tall as its title; the neighbouring card’s title row is as tall as that title. Nothing connects them, because each card only knows about its own content.

What you want is for the “title row” to be one track shared across all cards in the same visual row, sized by the tallest title. That shared track has to live in the parent, the .sessions grid, and the cards need to borrow it.

Borrowing the parent’s rows

sessions.css
.sessions {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
  gap: 24px;
}

.session-card {
  display: grid;
  grid-row: span 4;              /* time, title, abstract, room */
  grid-template-rows: subgrid;   /* use the parent's four rows */
  row-gap: 8px;                  /* tighter than the parent's 24px */
}

Two declarations do the work. grid-row: span 4 makes each card occupy four rows of the parent grid. grid-template-rows: subgrid tells the card not to create its own rows but to use the four it spans. The card’s four children (time, title, abstract, room) are placed into those parent rows in order.

Now the browser sizes each parent row by the tallest content placed in it from every card in that row. The title track grows to fit the three-line title, the shorter titles get the same track, and every abstract starts at the same line. Room labels line up at the bottom.

Left: three cards whose title blocks have different heights, so the room labels below sit at different heights. Right: the same three cards with shared row lines, so titles share one tall row and the room labels align. Own rows rooms misaligned Subgrid rows rows shared, aligned
Without subgrid each card sizes its own rows; with subgrid they share the parent’s rows

Notice that the parent never declares grid-template-rows. It doesn’t need to: the cards’ spans create implicit rows, and those rows are sized from the content the subgrids place in them. Every new row of cards gets its own four tracks, so cards in the second visual row align with each other but not with the first row, which is what you want.

Gaps, lines and columns

A subgrid inherits the parent’s gap. Here the parent uses 24px between cards, which is far too much between a title and its abstract, so the card overrides row-gap for its own tracks. The space between cards is unaffected.

Subgrid works on columns too. A classic case on Waypoint is the speaker profile form: labels in one column, inputs in the next, and every label column the same width across several fieldsets. Making each fieldset grid-column: span 2; grid-template-columns: subgrid gives every fieldset the outer form’s columns, so labels align all the way down. Named grid lines from the parent are also visible inside the subgrid, so grid-column: content-start / content-end keeps working one level down.

Your turn

The exercise gives you three session cards with titles of different lengths. Turn each card into a row subgrid so that abstracts and room labels line up across the row. The checks compare the vertical positions of those parts across cards. The next lesson stays with sizing: min(), max(), clamp() and the keywords that let content decide how big things are.

Key takeaways

  • A nested grid with grid-template-rows: subgrid uses its parent’s row tracks instead of creating its own, so content in sibling cards shares the same rows.
  • The subgridded item must span as many parent tracks as it has parts, for example grid-row: span 4 for a four-part card.
  • A subgrid inherits the parent’s gap by default and can override it with its own row-gap or column-gap.
  • Subgrid has been Baseline since September 2023 and widely available since March 2026, so aligned card layouts no longer need fixed heights or JavaScript measuring.

Further reading

Check your understanding

3 questions

Question 1 of 3

Each card has four children (time, title, abstract, room). Why don’t the rooms line up?

CSS
.sessions { display: grid; grid-template-columns: repeat(3, 1fr); }
.session-card { display: grid; grid-template-rows: subgrid; }
Question 2 of 3

What’s the most robust way to make every card’s room label align across a row when titles wrap to different numbers of lines?

Question 3 of 3

The parent grid has gap: 24px, and the subgridded cards look too spread out inside. What do you change?

Practice

Code playground

Line up the session cards

The three cards sit in a three-column grid, but their titles wrap to different numbers of lines, so the abstracts and room labels sit at different heights. Make each .session-card a row subgrid that spans the four parts (time, title, abstract, room), so that every abstract starts at the same height and every room label lines up. Keep the cards side by side and don’t use fixed heights.

<section class="sessions">
  <article class="session-card">
    <p class="time">09:30</p>
    <h3>Subgrid</h3>
    <p class="abstract">Aligning card content across a row without fixed heights.</p>
    <p class="room">Room A</p>
  </article>
  <article class="session-card">
    <p class="time">10:15</p>
    <h3>Container Queries in a Design System With Forty Components</h3>
    <p class="abstract">What changed when every component started measuring itself.</p>
    <p class="room">Room B</p>
  </article>
  <article class="session-card">
    <p class="time">11:00</p>
    <h3>Color Spaces for Humans Who Ship Interfaces Every Day of the Week</h3>
    <p class="abstract">oklch(), color-mix() and palettes that stay accessible.</p>
    <p class="room">Main hall</p>
  </article>
</section>Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

Preview

Console

(no output)

Checks

  • The cards are still side by side
  • Each card uses grid-template-rows: subgrid
  • All abstracts start at the same height
  • All room labels line up
  • Nothing inside a card overlaps