Grid Advanced

⚡ Chapter 4 — Grid Advanced

Chapter 3 covered the mechanics of CSS Grid — defining tracks, placing items, and naming areas. This chapter pushes into the techniques that make Grid feel like a superpower: auto-fit and auto-fill for truly responsive layouts without a single media query, named grid lines for surgical placement, dense packing for gallery-style grids, and stacking items inside the same cell. By the end, you will be writing professional-grade layouts that adapt intelligently to any screen size.

1 — auto-fill vs auto-fit

Both keywords go inside repeat() as a replacement for a fixed count. Instead of telling the browser how many tracks to create, you tell it to create as many as fit:

🎨 CSS — syntax
/* Instead of a fixed number… */ grid-template-columns: repeat(3, 200px); /* …let the browser decide how many tracks fit */ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

The difference only shows up when items do not fill all available space — for example, you have 3 cards but the container is wide enough for 6.

  • auto-fill — creates as many tracks as the minimum allows, including empty ones. The filled items share space with those phantom tracks.
  • auto-fit — creates the same tracks, but then collapses the empty ones to zero width. The filled items expand to take up all the available space.
Live demo — auto-fill vs auto-fit side by side
auto-fill
1
2
auto-fit
1
2
auto-fill keeps empty tracks → items stay narrow. auto-fit collapses them → items expand.
When does the difference matter? In practice, auto-fit is almost always what you want for card grids and galleries — you want items to fill the available width. Use auto-fill when you want the grid to reserve space for items that might appear later (e.g., a fixed-width grid that gets items added dynamically), or when you want to prevent cards from stretching too wide when there are only a few of them.

2 — The Intrinsically Responsive Grid

Combine auto-fit with minmax() and you get the most powerful layout one-liner in CSS — a grid that automatically adjusts its column count to the container width, with zero media queries:

🎨 CSS — the RAM pattern (Repeat, Auto, Minmax)
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 24px; } /* Reading this declaration: "Create as many 1fr columns as will fit, where each column is at least 240px wide" At 1200px: fits 4 columns (300px each) At 900px: fits 3 columns (300px each) At 560px: fits 2 columns (280px each) At 250px: fits 1 column (250px) No media queries. No breakpoints. Just math. */
This is sometimes called the "RAM" pattern — Repeat, Auto-fit, Minmax. It's one of the most widely used CSS Grid patterns in production.
Live demo — auto-fit: minmax(110px, 1fr) at different container widths
Card 1
Card 2
Card 3
Card 4
Card 5
Card 6
/* container width: 75% — items reflow automatically */ grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
⚠️ Don't use 0 as the minimum. repeat(auto-fit, minmax(0, 1fr)) gives you the same behaviour as repeat(auto-fit, 1fr) — columns never collapse because 0 is always satisfiable. Always use a meaningful pixel minimum that reflects the actual smallest usable size for the content inside.
💡 Capping item width. To stop cards from growing too wide on very large screens, pair the RAM pattern with max-width on the grid: .card-grid { max-width: 1200px; margin-inline: auto; }. The grid reflows automatically up to 1200px, then the container centres.

3 — Named Grid Lines

Grid lines have numbers by default (1, 2, 3… and -1, -2, -3 from the end). You can also give them names using square brackets in grid-template-columns or grid-template-rows. Named lines make placement declarations read like English instead of coordinates.

🎨 CSS — defining and using named lines
.layout { display: grid; /* Name the lines in brackets before/after each track size */ grid-template-columns: [full-start] 1fr [main-start] minmax(0, 720px) [main-end] 1fr [full-end]; grid-template-rows: [header-start] 60px [header-end content-start] 1fr [content-end]; /* A line can have multiple names — separate them with a space */ } /* Place items by name instead of number */ .hero { grid-column: full-start / full-end; /* full bleed */ grid-row: header-start / content-end; } .article { grid-column: main-start / main-end; /* constrained centred column */ }
Named lines are especially valuable in complex editorial layouts where you have a "full-bleed" outer zone and a centred "content" zone — you give items one of two names and they snap to the right zone without counting line numbers.
Named lines from grid-template-areas. When you define grid-template-areas, the browser automatically creates named lines for you — every area named foo generates column lines foo-start and foo-end, and the same for rows. You can use those auto-generated names in grid-column and grid-row even if you are not using grid-area on the item itself.
🎨 CSS — using the auto-named lines from grid-template-areas
.page { display: grid; grid-template-columns: 200px 1fr; grid-template-areas: "header header" "sidebar main" "footer footer"; } /* These named lines are created automatically: column: header-start, header-end, sidebar-start, sidebar-end / main-start, main-end, footer-start, footer-end row: header-start, header-end, sidebar-start, sidebar-end, main-start, main-end, footer-start, footer-end */ /* A decorative overlay that sits in the "main" column */ .overlay { grid-column: main-start / main-end; grid-row: header-start / footer-end; }

4 — grid-template-areas in Depth

You covered the basics in Chapter 3. Let's go deeper with a real magazine-style layout that mixes multiple area sizes, uses dots for empty cells, and rewires completely on mobile.

🎨 CSS — complex magazine layout
.magazine { display: grid; grid-template-columns: 2fr 1fr 1fr; grid-template-rows: auto auto auto auto; gap: 16px; grid-template-areas: "hero hero promo" /* row 1 */ "hero side1 promo" /* row 2 — hero spans 2 rows */ "feature side1 side2" /* row 3 — side1 spans 2 rows */ "feature . side2"; /* row 4 — dot = empty cell */ } .hero { grid-area: hero; } .promo { grid-area: promo; } .side1 { grid-area: side1; } .side2 { grid-area: side2; } .feature { grid-area: feature; } /* On tablet: 2-column with different region arrangement */ @media (max-width: 800px) { .magazine { grid-template-columns: 1fr 1fr; grid-template-areas: "hero hero" "promo side1" "feature side2"; } } /* On mobile: single column, reading order */ @media (max-width: 500px) { .magazine { grid-template-columns: 1fr; grid-template-areas: "hero" "feature" "promo" "side1" "side2"; } }
The dot (.) creates an intentionally empty cell — useful for maintaining structural alignment when not every zone has content at every breakpoint.
⚠️ Each named area must be a rectangle. The names in your area map must tile into contiguous rectangles — no L-shapes, no disconnected islands. The browser silently ignores an invalid grid-template-areas declaration, so a layout that looks correct may be falling back silently. Check DevTools grid overlay when things look off.

5 — Dense Packing with grid-auto-flow: dense

When some items span multiple columns or rows, the auto-placement algorithm leaves gaps — cells it cannot fill because the next item is too wide. By default those gaps remain empty. Adding dense instructs the browser to go back and fill them with later (smaller) items.

Live demo — mixed-size items in a 4-column grid
A (×2)
B
C
D (×3)
E
F
G (×2)
H
I
J (×2)
grid-auto-flow: row; /* default — gaps remain */
⚠️ Dense changes the visual order. Items are reordered visually (but not in the DOM) to fill gaps. This creates a mismatch between the reading/tab order (DOM) and the visual order, which is an accessibility concern. Use dense only for purely decorative grids (image galleries, tile walls) where the visual order does not need to match the logical order. Never use it where each item has a meaningful position.
🎨 CSS — dense packing for an image gallery
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-auto-rows: 200px; grid-auto-flow: dense; /* back-fill any gaps */ gap: 8px; } /* Landscape photos span 2 columns, portraits are 1×1 */ .gallery .landscape { grid-column: span 2; } .gallery .tall { grid-row: span 2; } .gallery .featured { grid-column: span 2; grid-row: span 2; } .gallery img { width: 100%; height: 100%; object-fit: cover; }

6 — Overlapping Items and z-index

Unlike Flexbox, CSS Grid lets you place multiple items into the same cell or area. They stack on top of each other. Use z-index to control which appears in front — and unlike static positioned elements, grid items respond to z-index without needing position: relative.

Static demo — text overlaid on an image card
Featured Article
How CSS Grid changed the way we build layouts
🎨 CSS — text overlay on a card using Grid stacking
.card { display: grid; grid-template: 1fr / 1fr; /* one row, one column */ height: 240px; border-radius: 8px; overflow: hidden; } /* All three children share the same cell — they stack */ .card > * { grid-column: 1; grid-row: 1; } .card__image { z-index: 1; object-fit: cover; width: 100%; height: 100%; } .card__overlay { z-index: 2; background: linear-gradient(to top, rgba(0,0,0,.8) 0%, transparent 60%); } .card__text { z-index: 3; display: flex; flex-direction: column; justify-content: flex-end; padding: 20px; }
grid-template: 1fr / 1fr is a shorthand for grid-template-rows: 1fr; grid-template-columns: 1fr; — a single row and single column, each taking all available space.

7 — Advanced Responsive Patterns

Pattern 1 — Sidebar that wraps below content

🎨 CSS — sidebar that collapses automatically
.with-sidebar { display: grid; grid-template-columns: minmax(260px, 1fr) minmax(0, 2fr); gap: 24px; } /* When the container is narrower than 520px (260+260), the second column can no longer maintain its minimum and the layout naturally breaks to a single column. No media query needed. */
This pattern uses minmax on both columns. When the container cannot satisfy both minimums simultaneously, the grid implicitly wraps — but only works when grid-template-columns creates 2 columns in the first place. Pair with a media query if you need guaranteed wrapping at a specific breakpoint.

Pattern 2 — Full-bleed sections in a constrained layout

🎨 CSS — three-zone layout for article pages
/* Three columns: edge | content | edge Content is capped at 720px and centred. Some sections can "break out" to full width. */ .article-page { display: grid; grid-template-columns: [full-start] 1fr [content-start] minmax(0, 720px) [content-end] 1fr [full-end]; } /* All direct children default to the content zone */ .article-page > * { grid-column: content; /* shorthand: content-start / content-end */ } /* Full-bleed hero, pull-quotes, or banners break out */ .full-bleed { grid-column: full; /* full-start / full-end */ }
When you use a named area like content or full in a shorthand, the browser expands it to content-start / content-end automatically — this works because named lines ending in -start and -end are treated as named areas by the placement algorithm.

Pattern 3 — Equal-height rows across multiple cards

🎨 CSS — vertically aligned card interiors using nested Grid
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); grid-auto-rows: 1fr; /* all rows same height */ align-items: stretch; } /* Each card is itself a grid — interior rows align across cards */ .card { display: grid; grid-template-rows: auto 1fr auto; /* header | body | footer */ } /* card__header: auto height */ /* card__body: grows (1fr) */ /* card__footer: auto (button) — always pinned to bottom */

8 — Quick Reference

auto-fill vs auto-fit

KeywordEmpty tracksItem width with few itemsBest for
auto-fill Kept (phantom slots) Stays at minimum — does not stretch Grids where empty slots should reserve space
auto-fit Collapsed to 0 Expands to fill the container Card grids, galleries — most common use case

grid-auto-flow values

ValueBehaviourWarning
row Fill rows left→right, new row when full (default)
column Fill columns top→bottom, new column when full
dense Back-fill gaps left by spanning items Visual ≠ DOM order — accessibility risk
row dense Row direction + dense back-fill Same accessibility warning

Named line syntax

PatternEffect
[name] before a trackNames the line before that track
[name1 name2]One line can have multiple names
grid-column: name-start / name-endPlace item using line names
grid-column: name (single name)Shorthand — expands to name-start / name-end if both exist
Auto-names from grid-template-areasArea "foo" creates foo-start, foo-end on both axes

✏️ Exercises

Each exercise builds on techniques from this chapter. Attempt the solution before revealing the answer.

Exercise 1
Create a responsive image gallery using the RAM pattern. Cards should be at least 180px wide and expand to fill available space. All rows should be 220px tall. Images should use object-fit: cover. Every third card (nth-child) should span 2 columns (a featured photo). Use grid-auto-flow: dense so the featured cards don't leave gaps.
Hint: repeat(auto-fit, minmax(180px, 1fr)). Give .card:nth-child(3n) a grid-column: span 2. Add grid-auto-flow: dense to the container.
CSS
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); grid-auto-rows: 220px; grid-auto-flow: dense; gap: 8px; } .gallery .card:nth-child(3n) { grid-column: span 2; } .gallery img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; }
Exercise 2
Build an article page layout where all content is constrained to a 680px centred column, but any element with class .full-bleed breaks out to the full container width. Use named grid lines ([full-start], [content-start], [content-end], [full-end]) and set all direct children to the content zone by default.
Hint: Three columns — 1fr [content-start] minmax(0, 680px) [content-end] 1fr. Set .article > * to grid-column: content-start / content-end. Override with .full-bleed { grid-column: full-start / full-end; }.
CSS
.article { display: grid; grid-template-columns: [full-start] 1fr [content-start] minmax(0, 680px) [content-end] 1fr [full-end]; } .article > * { grid-column: content-start / content-end; } .full-bleed { grid-column: full-start / full-end; }
Exercise 3
Create a card component where a background image, a gradient overlay, and a text block all share the same grid cell, stacking visually. The text should always appear at the bottom of the card. The card should be 280px tall. Use only display: grid for the stacking — no absolute positioning.
Hint: Set the card to a single-cell grid (grid-template: 1fr / 1fr). Give all children grid-column: 1; grid-row: 1. Use z-index to layer them. Make the text container display: flex; align-items: flex-end.
HTML
<div class="card"> <img class="card__image" src="photo.jpg" alt=""> <div class="card__overlay"></div> <div class="card__text"> <h2>Card Title</h2> <p>A short description</p> </div> </div>
CSS
.card { display: grid; grid-template: 1fr / 1fr; height: 280px; border-radius: 10px; overflow: hidden; } .card > * { grid-column: 1; grid-row: 1; } .card__image { z-index: 1; width: 100%; height: 100%; object-fit: cover; } .card__overlay { z-index: 2; background: linear-gradient(to top, rgba(0,0,0,.8) 0%, transparent 55%); } .card__text { z-index: 3; display: flex; flex-direction: column; justify-content: flex-end; padding: 20px; color: #fff; }
Exercise 4
Define a 3-column magazine grid using grid-template-areas with these zones: hero (spans all 3 columns, 2 rows), lead (column 1, rows 3–4), featured (columns 2–3, row 3), aside (column 2, row 4), promo (column 3, row 4). Then write a media query for screens under 700px that collapses it to a single column in reading order: hero → lead → featured → aside → promo.
Hint: Draw the 4-row area map first. Hero spans rows 1–2 with the same name in both rows across all columns. Then define a 5-row single-column version in the media query.
CSS
.magazine { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; grid-template-areas: "hero hero hero" "hero hero hero" "lead featured featured" "lead aside promo"; } .hero { grid-area: hero; } .lead { grid-area: lead; } .featured { grid-area: featured; } .aside { grid-area: aside; } .promo { grid-area: promo; } @media (max-width: 700px) { .magazine { grid-template-columns: 1fr; grid-template-areas: "hero" "lead" "featured" "aside" "promo"; } }