Skip to content
AgileLogics
7 min read

CSS Grid in Depth

Prerequisites: CSS Grid basics

Named areas

grid-template-areas draws the layout as text:

.page {
  display: grid;
  grid-template-columns: 14rem 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 1rem;
}

.page > header { grid-area: header; }
.page > nav { grid-area: sidebar; }
.page > main { grid-area: main; }
.page > footer { grid-area: footer; }

@media (width < 40rem) {
  .page {
    grid-template-columns: 1fr;
    grid-template-areas: "header" "main" "sidebar" "footer";
  }
}

Each string is a row; repeating a name spans cells; a . leaves a cell empty. Areas must be rectangles. Changing the layout for small screens is just a new map.

Remember that screen readers and keyboard users follow the HTML order, not the visual grid order. Keep them consistent.

Implicit tracks and auto-placement

.feed {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(10rem, auto);
  grid-auto-flow: dense;
}
  • grid-auto-rows sizes rows created automatically.
  • grid-auto-flow: dense back-fills holes left by large items (which can put items out of source order visually).

Named lines

.article {
  display: grid;
  grid-template-columns:
    [full-start] minmax(1rem, 1fr)
    [content-start] minmax(0, 65ch)
    [content-end] minmax(1rem, 1fr)
    [full-end];
}
.article > * { grid-column: content; }
.article > .full-bleed { grid-column: full; }

Text sits in a readable center column; images marked .full-bleed stretch edge to edge. content refers to the area between content-start and content-end.

subgrid

Nested grids normally have their own tracks, so the titles, texts, and buttons of neighboring cards don't line up. subgrid lets a child grid reuse its parent's tracks:

<style>
  .cards { display: grid; grid-template-columns: repeat(2, 200px); gap: 0 20px; }
  .card { display: grid; grid-row: span 3; grid-template-rows: subgrid; }
</style>
<div class="cards">
  <article class="card"><h3>Short</h3><p>One line.</p><a href="/a">Buy</a></article>
  <article class="card"><h3>A much longer title that wraps onto several lines</h3><p>One line.</p><a href="/b">Buy</a></article>
</div>
<script>
  const [a, b] = [...document.querySelectorAll(".card")];
  console.log("paragraphs aligned:", a.querySelector("p").getBoundingClientRect().top === b.querySelector("p").getBoundingClientRect().top);
  console.log("buttons aligned:", a.querySelector("a").getBoundingClientRect().top === b.querySelector("a").getBoundingClientRect().top);
</script>
paragraphs aligned: true
buttons aligned: true

Both cards span three rows of the parent grid and use them for their title, text, and link, so the long title pushes the text down in both cards. subgrid works in all major browsers (Firefox 71, Safari 16, Chrome and Edge 117).

Masonry-style layouts

A native masonry layout, display: grid-lanes, is being standardized (Safari 26.4 was first to ship it). Until it's widely available, use CSS columns (columns: 3) for simple cases.

Practice

Write a grid-template-areas layout for a dashboard: a header across the top, a narrow nav on the left, main content, and a right-hand "stats" column; below 50rem, stack everything in one column.

Show solution
.dashboard {
  display: grid;
  grid-template-columns: 12rem 1fr 16rem;
  grid-template-areas:
    "header header header"
    "nav main stats";
  gap: 1rem;
}

@media (width < 50rem) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-areas: "header" "nav" "main" "stats";
  }
}

.dashboard > header { grid-area: header; }
.dashboard > nav { grid-area: nav; }
.dashboard > main { grid-area: main; }
.dashboard > aside { grid-area: stats; }