Browse by section

Web Design 日本語

2024 CSS Grid Updates and Applications

The genuinely new addition to CSS Grid in recent years is subgrid (Baseline September 2023). It solves a problem CSS could not previously express: aligning the heading and body of cards across a whole row.

This article was published in 2024 and revised in September 2026. The subgrid code originally shown does not work, because the element did not span multiple tracks of the parent grid. It has been corrected, with the patterns actually used in production added.

  • subgrid only means something when the element spans multiple tracks of its parent. A declaration such as grid-column: span 2 is required
  • grid-template-areas and minmax() date from Grid’s original release around 2017—they are not new

Sponsored

Grid versus Flexbox

The short version: Grid when you need to align in two directions, Flexbox when you are laying things out in one.

CSS Grid Flexbox
Dimensions Rows and columns together One axis (row or column)
Sizing The parent defines the tracks Content determines the size
Best for Page skeletons, card grids, tabular layout Navigation, button rows, centring
Visual definition grid-template-areas None

It is not about which is better—it is whether you have two directions to align.

subgrid: aligning nested elements to the parent’s tracks

This closed Grid’s biggest gap. Grid could only align its direct children; anything inside them formed an unrelated grid.

The problem

In a card grid, a title that wraps to two lines pushes that card’s body text out of line with the others.

<ul class="cards">
  <li class="card">
    <h3>Short title</h3>
    <p>Body...</p>
    <a href="#">Read more</a>
  </li>
  <li class="card">
    <h3>A noticeably longer title that wraps onto two lines</h3>
    <p>Body...</p>
    <a href="#">Read more</a>
  </li>
</ul>

The subgrid solution

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  /* define the three rows on the parent */
  grid-template-rows: auto 1fr auto;
  gap: 24px;
}

.card {
  display: grid;
  /* span the parent's three rows — without this, subgrid does nothing */
  grid-row: span 3;
  grid-template-rows: subgrid;
  gap: 8px;

  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 12px;
}

Now the body text and the link line up across every card, because the row heights are shared along the row.

The mistake to avoid

This does not work—it is what the article originally showed.

/* .child occupies one column, so there are no tracks to inherit */
.parent {
  display: grid;
  grid-template-columns: 1fr 2fr;
}
.child {
  display: grid;
  grid-template-columns: subgrid;
}
/* make it span all the parent's columns */
.parent {
  display: grid;
  grid-template-columns: 1fr 2fr;
}
.child {
  grid-column: 1 / -1;   /* ← required */
  display: grid;
  grid-template-columns: subgrid;
}

The rule is simply: if it does not span, there is nothing to inherit.

gap and line names

  • gap is inherited from the parent. Override it with row-gap / column-gap on the child if needed
  • Line names defined on the parent are usable from the child, and the child can add its own
  • Using subgrid on both axes disables implicit track creation. Excess children get crammed into the last track, so consider grid-auto-rows for the block axis

Supported in Firefox 71, Safari 16 and Chrome/Edge 117, reaching Baseline in September 2023.

Sponsored

The responsive pattern you will use most

A card grid with no media queries at all.

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 24px;
}

The column count adjusts to the available width. No breakpoints required.

But it has a flaw

It overflows when the container is narrower than 260px, because the minimum in minmax() means “never smaller than this”.

.cards {
  display: grid;
  /* fall back to 100% when the container is narrow */
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 24px;
}

min(260px, 100%) prevents horizontal scrolling on small screens. That one addition changes how the page behaves on phones.

auto-fit versus auto-fill

auto-fit auto-fill
With few items Collapses empty tracks; items stretch Keeps empty tracks; items stay their size
Use when You always want to fill the width You want alignment across rows

If a single item should not balloon to full width, use auto-fill. Otherwise auto-fit is usually right.

grid-template-areas: drawing the layout

.layout {
  display: grid;
  grid-template-areas:
    "header header"
    "main   sidebar"
    "footer footer";
  grid-template-columns: 1fr 280px;
  grid-template-rows: auto 1fr auto;
  min-height: 100dvh;
  gap: 24px;
}

.layout > header  { grid-area: header; }
.layout > main    { grid-area: main; }
.layout > aside   { grid-area: sidebar; }
.layout > footer  { grid-area: footer; }

@media (width < 768px) {
  .layout {
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "footer";
    grid-template-columns: 1fr;
  }
}

Rearranging the area names changes the layout entirely—the HTML is untouched.

One caution: when the visual order diverges from DOM order, keyboard navigation order breaks. Moving a sidebar below the content as above is fine; wholesale reordering is not.

Sponsored

Useful smaller techniques

Overlapping elements

Placing several elements in the same cell stacks them—no position: absolute needed.

.hero {
  display: grid;
}

.hero > * {
  grid-area: 1 / 1;   /* everything in row 1, column 1 */
}

.hero__text {
  place-self: center;
  z-index: 1;
}

The parent’s height follows the image, which is easier to manage than position: relative plus explicit heights.

Spanning the full width

.full-bleed {
  grid-column: 1 / -1;  /* -1 is the last line */
}

Sizing implicit rows

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(120px, auto); /* height of generated rows */
}

With a variable number of items, omitting this gives you rows of inconsistent height.

Combining with container queries

A Grid component needs a different column count depending on where it sits. Branch on the parent’s width rather than the viewport’s.

.card-area {
  container-type: inline-size;
}

.card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

@container (width >= 400px) {
  .card {
    grid-template-columns: 120px 1fr;
  }
}

The same card is now stacked in a sidebar and side by side in the main column. More on this in CSS @media: the features that actually landed.

Debugging

Grid has dedicated developer tools. Guessing at numbers is the slow way.

  • Chrome / Edge: click the “grid” badge on an element to overlay track numbers and gaps
  • Firefox: the Layout panel’s Grid section overlays area names and line numbers. It is the better tool for debugging grid-template-areas

When something lands in the wrong place, visualise the track numbers first. It is almost always a miscounted line—lines start at 1, so three columns have four lines.

Summary

  • subgrid works only when the element spans parent tracks. grid-row: span 3 or similar is required
  • Its best use is aligning headings and body text across a row of cards
  • gap is inherited from the parent; line names are available too
  • The responsive pattern is repeat(auto-fit, minmax(min(260px, 100%), 1fr)). Without min() it overflows on narrow screens
  • auto-fit collapses empty tracks; auto-fill keeps them
  • Overlap by giving every child grid-area: 1 / 1
  • Set grid-auto-rows when the item count varies
  • Component-level branching belongs to @container
  • grid-template-areas and minmax() are from 2017

For spacing, see adding space in flexbox and grid with the gap property.