Skip to content

CSS Grid Advanced Patterns

beginner11 min read

Grid Gets Powerful When You Stop Counting Lines

Grid fundamentals are about numbers -- line 1 to line 3, span 2. But advanced Grid? It's about names, nesting, and overlapping. Named areas make layouts readable as a visual map. Subgrid solves the nested alignment problem that used to require JavaScript. And overlapping items eliminate absolute positioning hacks entirely.

Mental Model

Think of advanced Grid as architectural blueprints with named rooms. Instead of saying "the kitchen spans coordinates (2,1) to (4,2)," you draw the floor plan and label each area: "header," "sidebar," "content," "footer." Anyone reading the blueprint immediately understands the layout. Subgrid is like a room within a room that aligns its internal walls with the building's main grid lines.

Named Grid Areas

.layout {
  display: grid;
  grid-template-areas:
    "header  header  header"
    "sidebar content aside"
    "footer  footer  footer";
  grid-template-columns: 250px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  min-height: 100dvh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

The grid-template-areas property is a visual map of the layout. Each string is a row. Each word is a column cell. Same name across cells = that area spans those cells.

Responsive Named Areas

/* Mobile: single column stack */
@media (max-width: 768px) {
  .layout {
    grid-template-areas:
      "header"
      "content"
      "sidebar"
      "aside"
      "footer";
    grid-template-columns: 1fr;
  }
}

The named areas stay the same — only the template map changes. Items automatically reflow to their new positions.

Empty Cells

.layout {
  grid-template-areas:
    "header header header"
    ".      content aside"  /* Dot = empty cell */
    "footer footer  footer";
}

Subgrid

This is the feature CSS developers waited years for. Subgrid lets a nested grid inherit track sizing from its parent grid. Without subgrid, nested grids are independent -- their columns don't align with the parent's columns.

.parent {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  gap: 1rem;
}

.child {
  grid-column: 1 / -1; /* Spans all parent columns */
  display: grid;
  grid-template-columns: subgrid; /* Inherits parent's column tracks */
  /* Now child's internal items align to the parent's grid lines */
}

Practical Subgrid: Card with Aligned Content

.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

.card {
  display: grid;
  grid-template-rows: subgrid; /* Inherit row tracks from parent? */
  /* Problem: parent doesn't define rows — subgrid needs explicit rows */
}

/* Better: Define parent rows explicitly */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, auto); /* 3 row tracks per card */
  gap: 1.5rem;
}

.card {
  grid-row: span 3; /* Each card spans 3 row tracks */
  display: grid;
  grid-template-rows: subgrid; /* Align title/body/footer across cards */
}

Now every card's title, body, and footer align on the same row lines across all cards — even with different content lengths.

Subgrid vs display: contents

Before subgrid, the workaround was display: contents on the wrapper — making its children direct grid participants of the grandparent grid. But display: contents removes the wrapper from the box model entirely (no background, padding, border). Subgrid lets the wrapper exist as a full box while its children align to the parent grid.

Overlapping Grid Items

Wait, grid items can occupy the same cells? Yes. And it enables layered layouts without position: absolute:

.hero {
  display: grid;
  grid-template-rows: 1fr auto 1fr;
}

.hero-image {
  grid-area: 1 / 1 / -1 / -1; /* Fill the entire grid */
}

.hero-text {
  grid-area: 2 / 1 / 3 / -1; /* Middle row, all columns */
  z-index: 1; /* Layer above the image */
  text-align: center;
  color: white;
}

No absolute positioning, no wrapper shenanigans. Items simply share cells.

/* Card with overlapping badge */
.card {
  display: grid;
}

.card > * {
  grid-area: 1 / 1; /* All children in the same cell */
}

.badge {
  align-self: start;
  justify-self: end;
  z-index: 1;
  margin: 0.5rem;
}

Responsive Grid Without Media Queries

This is where it all comes together.

/* Fully responsive: 1 column on mobile, fills as many as fit on desktop */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 1.5rem;
}

The min(300px, 100%) handles the edge case where the container is narrower than 300px — the column shrinks to 100% instead of overflowing.

Responsive Sidebar Layout Without Media Queries

.layout {
  display: grid;
  grid-template-columns: fit-content(300px) minmax(0, 1fr);
  gap: 2rem;
}

/* Or: sidebar collapses to stack when space is tight */
.layout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr));
}
Quiz
A grid uses repeat(auto-fill, minmax(200px, 1fr)) in a 900px container. How many column tracks are created?
Execution Trace
Template areas
Define visual map: header/sidebar/content/footer
Each string is a row, each word a column cell
Name mapping
grid-area: header → element placed in 'header' cells
Automatic line naming: header-start, header-end
Subgrid
Child uses grid-template-columns: subgrid
Inherits parent's column tracks for alignment
Overlap
Two items placed in grid-area: 1/1/-1/-1
Both occupy the same cells, z-index controls stacking
auto-fit
minmax(min(300px, 100%), 1fr)
Responsive columns without media queries
What developers doWhat they should do
Using the same area name in non-rectangular shapes
The spec requires areas to be rectangular. Non-rectangular areas make the entire grid-template-areas declaration invalid.
Grid areas must form rectangles. L-shapes or T-shapes are invalid and silently ignored.
Expecting subgrid to work without the child spanning parent tracks
subgrid inherits tracks for the spanned range only. A child in 1 column can't inherit 3 parent columns.
A subgrid child must span the parent tracks it wants to inherit
Using position: absolute for overlapping elements when grid can do it
Grid overlap keeps items in flow. Absolute positioning removes them and requires manual sizing.
Place multiple items in the same grid-area and use z-index
Writing minmax(300px, 1fr) without handling narrow containers
If the container is 280px, minmax(300px, 1fr) forces a 300px track that overflows. min(300px, 100%) caps at container width.
Use minmax(min(300px, 100%), 1fr) so the minimum doesn't cause overflow on small screens
Quiz
What happens if grid-template-areas defines an L-shaped area like this: 'header header' 'sidebar .'?
Quiz
A child grid uses grid-template-columns: subgrid and spans 3 columns of its parent. How many column tracks does the child have?
Key Rules
  1. 1Named grid areas must form rectangles — L-shapes or T-shapes are invalid
  2. 2Subgrid inherits parent tracks only for the range the child spans
  3. 3Grid items can overlap by sharing the same grid-area — no absolute positioning needed
  4. 4Use minmax(min(300px, 100%), 1fr) with auto-fit for bulletproof responsive grids
  5. 5grid-template-areas is a visual map — make it readable by aligning the columns