Subgrid

CSS · Lesson 29 of 55 6 min read Updated

Here is a problem you will meet on almost every website: a row of cards where the titles have different lengths. One title fits on one line, another needs two, and suddenly the descriptions and buttons inside the cards no longer line up. It looks untidy, and before subgrid there was no clean CSS fix.

Subgrid solves it. It lets an element inside a grid reuse its parent's rows or columns, so content in different cards can share the same tracks. In this lesson you will use subgrid for card rows and for a neat form layout.

Why nested grids do not line up

Imagine a grid of cards where each card is also a grid with three rows: title, text and button. Each card's grid is independent. The title row in card one is sized by card one's title only. It has no idea that the title in card two needs two lines. So each card ends up with different row heights, and the buttons sit at different heights.

What we want is for all the cards in a row to share one set of row heights: the title row as tall as the tallest title, the text row as tall as the longest text. That is exactly what subgrid does.

How subgrid works

To use subgrid on rows, the child element needs three things:

  1. display: grid, so it is a grid itself.
  2. A span of rows in the parent grid, for example grid-row: span 3 for three pieces of content.
  3. grid-template-rows: subgrid, which says "do not make my own rows, use the parent's rows that I span".

The children of the card are now laid out on the parent's row tracks. Because every card in the same row of the page spans the same three parent rows, their titles, texts and buttons line up.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Subgrid cards</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 16px; }
    .courses {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
      gap: 16px;
    }
    .card {
      display: grid;
      grid-row: span 3;
      grid-template-rows: subgrid;
      row-gap: 8px;
      padding: 16px;
      border: 1px solid #cbd5e1;
      border-radius: 10px;
    }
    .card h2 { margin: 0; font-size: 1.1rem; }
    .card p  { margin: 0; color: #475569; }
    .card a  {
      justify-self: start;
      padding: 6px 12px;
      border-radius: 6px;
      background: #1d4ed8;
      color: white;
      text-decoration: none;
    }
  </style>
</head>
<body>
  <div class="courses">
    <article class="card">
      <h2>Tally basics</h2>
      <p>Learn vouchers and ledgers.</p>
      <a href="#">Details</a>
    </article>
    <article class="card">
      <h2>Spoken English for interviews and group discussions</h2>
      <p>Weekend batches in Rohtak with mock interviews and feedback from trainers.</p>
      <a href="#">Details</a>
    </article>
    <article class="card">
      <h2>Basic computer skills</h2>
      <p>Typing, email and online forms.</p>
      <a href="#">Details</a>
    </article>
  </div>
</body>
</html>

All three "Details" buttons sit on the same line, and the descriptions start at the same height. Now delete the line grid-template-rows: subgrid; and run it again. Each card makes its own rows, and the buttons jump to different heights.

Gaps inside a subgrid

A subgrid uses the parent's gaps by default. In the example above the parent has a gap of 16px, which is good between cards but too loose between a title and its text. Setting row-gap: 8px on the card gives the inside of the card its own, smaller gap while the cards still share the same tracks.

Subgrid on columns: a neat form

Subgrid works on columns too, with grid-template-columns: subgrid. A good example is a form where each label and input pair is wrapped in its own element. The wrapper is useful for spacing, borders or hint text, but it normally stops the labels from lining up with each other. With subgrid, all the labels share the parent's first column.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Subgrid form</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 16px; }
    form {
      display: grid;
      grid-template-columns: max-content 1fr;
      gap: 12px 16px;
      max-width: 480px;
    }
    .field {
      display: grid;
      grid-column: 1 / -1;
      grid-template-columns: subgrid;
      align-items: center;
    }
    input { padding: 6px; font: inherit; }
  </style>
</head>
<body>
  <form>
    <div class="field">
      <label for="name">Name</label>
      <input id="name" value="Rohan Mehta">
    </div>
    <div class="field">
      <label for="phone">Mobile number</label>
      <input id="phone" type="tel">
    </div>
    <div class="field">
      <label for="city">City</label>
      <input id="city" value="Pune">
    </div>
  </form>
</body>
</html>

The parent's first column is max-content, so it becomes as wide as the longest label, "Mobile number". Each .field spans both columns with grid-column: 1 / -1 and adopts them with subgrid, so every input starts at the same point.

Line names pass through

A subgrid also inherits the line names of its parent. This pairs nicely with the named-line layout from Grid Placement & Areas. A full-width band can stretch edge to edge while the text inside it still lines up with the centre column of the page.

CSS
.page {
  display: grid;
  grid-template-columns:
    [full-start] 1fr [content-start] minmax(0, 60rem) [content-end] 1fr [full-end];
}

.band {
  grid-column: full;              /* the coloured band is edge to edge */
  display: grid;
  grid-template-columns: subgrid; /* but it reuses the page columns */
  background: #fef3c7;
}

.band > * {
  grid-column: content;           /* so its text lines up with the page text */
}

Without subgrid you would have to repeat the same column sizes on the band and keep the two in step by hand.

Common mistakes

  • Forgetting display: grid on the child. grid-template-rows: subgrid only works on an element that is itself a grid.
  • Not spanning enough tracks. The child must span as many parent tracks as it has children to place. Three pieces of content need grid-row: span 3.
  • Using subgrid on a grandchild. Subgrid only connects an element to its direct parent grid. Each level in between must be a subgrid too.
  • Expecting the parent's gap to be right everywhere. Override row-gap or column-gap on the subgrid when the inside needs different spacing.

Key points

  • Nested grids are normally independent, so content in different cards does not line up.
  • grid-template-rows: subgrid or grid-template-columns: subgrid makes a grid item reuse its parent's tracks.
  • The subgrid element must be a grid itself and must span the parent tracks it wants to use.
  • Subgrids inherit the parent's gaps and line names; you can override the gaps.
  • Use subgrid for aligned card rows, forms with wrapped fields and full-width bands with aligned content.

Exercise

Build a row of three "plan" cards for a mobile recharge shop in Kochi. Each card has four parts: plan name, price, a list of benefits and a "Choose" button. The benefits lists have different lengths. Use subgrid so that the prices, lists and buttons line up across all three cards.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Recharge plans</title>
  <style>
    .plans {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
      gap: 16px;
    }
    .plan {
      display: grid;
      grid-row: span 4;
      grid-template-rows: subgrid;
      row-gap: 8px;
      padding: 12px;
      border: 1px solid #ccc;
    }
    .plan h2, .plan p, .plan ul { margin: 0; }
  </style>
</head>
<body>
  <div class="plans">
    <section class="plan"><h2>Basic</h2><p>₹199</p><ul><li>1 GB per day</li></ul><button>Choose</button></section>
    <section class="plan"><h2>Popular</h2><p>₹299</p><ul><li>2 GB per day</li><li>Unlimited calls</li></ul><button>Choose</button></section>
    <section class="plan"><h2>Max</h2><p>₹449</p><ul><li>3 GB per day</li><li>Unlimited calls</li><li>100 SMS per day</li></ul><button>Choose</button></section>
  </div>
</body>
</html>

Each plan spans four parent rows because it has four children. With grid-template-rows: subgrid, the list row takes the height of the longest list, so all three buttons line up.

Quick Quiz

1. What problem does subgrid solve?

2. A card has three children and uses grid-template-rows: subgrid. What else does it need?

3. What gap does a subgrid use if you do not set one?

Learn CSS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call