Chapter 6 — Deep Dive: Grid
CSS Grid is a two-dimensional layout system — it places items on both rows and
columns simultaneously. While Flexbox distributes items along a single axis, Grid
lets you design the layout first and place content into it. It is the right tool for
page-level structure, dashboard layouts, card grids, and anything that benefits from
alignment across two dimensions at once.
1. Grid Lines and Tracks
A grid is defined by its lines. Lines are numbered starting at 1 from the
start edge. Between two adjacent lines is a track (column track or row track).
The area bounded by four lines is a cell. An item can span multiple cells to
form a grid area.
line 1
line 2
line 3
line 4
line 5
col 1 · row 1
col 2 · row 1
col 3 · row 1
col 4 · row 1
col 1 · row 2
col-start 2
row-start 2
col-end 4
row-end 3
col 4 · row 2
← column track 1 →
← column track 2 →
← column track 3 →
← column track 4 →
Blue item: grid-column: 2 / 4 · grid-row: 2 / 3 — spans from line 2 to line 4 horizontally, line 2 to line 3 vertically.
Negative line numbers count from the end: grid-column: 1 / -1 spans the full width regardless of column count.
2. Defining the Grid — Container Properties
| Property | Values | Notes |
| Track definition |
| grid-template-columns |
length · fr · auto · min-content · max-content · repeat() · minmax() |
Defines the explicit column tracks. Space-separated list. The most important grid property. |
| grid-template-rows |
same as columns |
Defines the explicit row tracks. Often left unset — rows grow to content height by default. |
| grid-template-areas |
quoted strings of area names |
Names regions of the grid visually. Each string is a row; each word is a cell. Use . for empty cells. Makes layouts readable at a glance. |
| grid-template |
rows / columns |
Shorthand for template-rows + template-columns + template-areas. Avoid — overly terse for most team codebases. |
| Implicit grid (auto-generated tracks) |
| grid-auto-columns |
length · fr · auto · minmax() |
Size of implicitly created column tracks (when items are placed outside the explicit grid). |
| grid-auto-rows |
length · fr · auto · minmax() |
Size of implicitly created row tracks. grid-auto-rows: minmax(100px, auto) is a common pattern for variable-height rows with a minimum. |
| grid-auto-flow |
row · column · dense · row dense · column dense |
row (default) — items fill rows left to right. column — fills columns top to bottom. dense — backfills gaps with smaller items (changes visual order — accessibility concern). |
| Gaps and alignment |
| gap / row-gap / column-gap |
length · percentage |
Same as flexbox. gap: 16px 24px sets row-gap 16px, column-gap 24px. |
| justify-items |
stretch · start · end · center |
Aligns items inline (horizontally) within their grid cell. Default stretch. Unlike flexbox, this property is meaningful in Grid. |
| align-items |
stretch · start · end · center · baseline |
Aligns items block (vertically) within their grid cell. |
| justify-content |
start · end · center · stretch · space-between · space-around · space-evenly |
Distributes the tracks themselves within the grid container when tracks are smaller than the container. Rarely needed with fr units (fr absorbs all space). |
| align-content |
same as justify-content |
Distributes row tracks vertically. Only applies when the total track height is less than the container height. |
3. Track Sizing — fr, minmax(), repeat()
The fr unit
/* fr = fraction of remaining space after fixed tracks are placed */
.grid {
grid-template-columns: 200px 1fr;
/* sidebar: fixed 200px. main: gets ALL remaining space */
}
.grid {
grid-template-columns: 1fr 2fr 1fr;
/* total 4 parts: left=25%, centre=50%, right=25% */
}
.grid {
grid-template-columns: 200px 1fr 160px;
/* left sidebar 200px, right sidebar 160px, middle gets everything left over */
}
/* fr vs % — fr is better because it accounts for gap */
/* 1fr 1fr with gap: 20px → each column = (container - 20px) / 2 */
/* 50% 50% with gap: 20px → total = container + 20px → overflow */
minmax()
/* minmax(minimum, maximum) — a track can't be smaller than min or larger than max */
.grid {
grid-template-columns: minmax(200px, 1fr) minmax(200px, 1fr);
/* each column: at least 200px, share remaining space equally */
}
.grid {
grid-auto-rows: minmax(100px, auto);
/* rows: at least 100px tall, grow to fit content */
}
/* min-content and max-content as minmax arguments */
.grid {
grid-template-columns: minmax(min-content, 200px) 1fr;
/* first column shrinks to the smallest content unit but never exceeds 200px */
}
repeat()
/* repeat(count, track-definition) */
.grid {
grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
grid-template-columns: repeat(4, 200px 1fr); /* 8 columns: 200px 1fr 200px 1fr… */
grid-template-columns: repeat(12, 1fr); /* classic 12-column grid */
}
/* auto-fill and auto-fit — responsive without media queries */
.grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
auto-fit vs auto-fill — the critical difference
auto-fit
Creates as many tracks as fit. Collapses empty tracks to zero width.
If you have 3 items and space for 5 columns, the 3 items grow to fill the full
container width. Items stretch to use all available space.
repeat(auto-fit, minmax(200px, 1fr))
auto-fill
Creates as many tracks as fit. Keeps empty tracks at their minimum size.
If you have 3 items and space for 5 columns, the 3 items stay at minimum size
and the 2 empty columns reserve space at the right edge.
repeat(auto-fill, minmax(200px, 1fr))
Use auto-fit for most card grids. You almost always want items to
grow and fill available space when there are fewer items than columns. Use auto-fill
when you want to preserve the column positions for future items — like a calendar
grid or a fixed-column dashboard that must stay aligned even when some cells are empty.
4. Named Template Areas
/* The CSS that produces the layout above */
.layout {
display: grid;
grid-template-columns: 140px 1fr 100px;
grid-template-rows: 44px 1fr 44px;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
gap: 8px;
min-height: 100dvh;
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
/* Responsive: stack on mobile by redefining the template areas */
@media (max-width: 600px) {
.layout {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"nav"
"main"
"aside"
"footer";
}
}
Named areas implicitly create named lines. Declaring
grid-area: header also creates lines named header-start
and header-end (both column and row). You can reference these in
grid-column: header-start / header-end — useful for overlapping items
onto named regions without repeating line numbers.
5. Item Placement Properties
/* Placing items by line number */
.item {
grid-column-start: 2;
grid-column-end: 4; /* spans from line 2 to line 4 = 2 column tracks */
grid-row-start: 1;
grid-row-end: 3; /* spans 2 row tracks */
}
/* Shorthand: grid-column: start / end */
.item {
grid-column: 2 / 4; /* line 2 to line 4 */
grid-column: 1 / -1; /* line 1 to last line — full width */
grid-column: 2 / span 3; /* start at line 2, span 3 tracks */
grid-column: span 2; /* auto-placed but spans 2 columns */
}
/* grid-area shorthand: row-start / col-start / row-end / col-end */
.item {
grid-area: 1 / 2 / 3 / 4; /* row 1→3, col 2→4 */
}
/* Individual item alignment override */
.item {
justify-self: center; /* inline axis — overrides justify-items */
align-self: end; /* block axis — overrides align-items */
place-self: center; /* shorthand: both axes */
}
6. Explicit vs Implicit Grid
/* Explicit grid — you define the tracks */
.grid {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 200px 200px;
/* Defines a 3×2 explicit grid */
}
/* If a 7th item is placed, it creates an IMPLICIT row */
/* By default implicit rows are sized to auto (content height) */
.grid {
grid-auto-rows: minmax(200px, auto);
/* All implicit rows: minimum 200px, grow to content */
}
/* grid-auto-flow: dense — backfills holes with smaller items */
.masonry-ish {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-flow: dense;
/* Items that span 2 columns leave a gap; dense fills it with the next small item */
/* WARNING: reorders items visually — bad for keyboard/screen reader users */
}
7. Named Lines
/* Lines can be named in square brackets */
.grid {
grid-template-columns:
[full-start] 1fr
[content-start] minmax(0, 800px) [content-end]
1fr [full-end];
}
/* Items then reference lines by name */
.full-bleed {
grid-column: full-start / full-end;
}
.content {
grid-column: content-start / content-end;
}
/* Great for article layouts where most content is centred */
/* but hero images, code blocks, etc. break out to full width */
8. Subgrid
Subgrid allows a nested grid to inherit and participate in its parent's track
definitions. Without subgrid, nested grids have completely independent track sizes —
columns can't align across cards. Supported in all modern browsers since late 2023.
/* The card alignment problem subgrid solves */
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto auto 1fr;
/* row 1: image · row 2: title · row 3: body · row 4: CTA */
}
.card {
display: grid;
grid-row: span 4; /* each card spans all 4 row tracks */
grid-template-rows: subgrid; /* inherit parent's row track sizes */
}
/* Now all card images, titles, and body text align horizontally */
/* across cards even when content lengths differ */
.card__image { grid-row: 1; }
.card__title { grid-row: 2; }
.card__body { grid-row: 3; }
.card__cta { grid-row: 4; }
9. Production Patterns
Responsive card grid (no media queries)
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
Full-page layout
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100dvh;
/* header · main · footer */
Sidebar layout
display: grid;
grid-template-columns:
minmax(200px, 25%) 1fr;
/* sidebar never below 200px */
Perfect centring
display: grid;
place-items: center;
/* single-line centering
works in any grid cell */
Overlapping items
/* multiple items, same area */
.grid { display: grid; }
.image { grid-area: 1/1/3/3; }
.caption { grid-area: 1/1/3/3;
z-index: 1;
align-self: end; }
Article with breakout
grid-template-columns:
[full-s] 1fr
[content-s] min(65ch,100%) [content-e]
1fr [full-e];
.content { grid-column: content; }
.full { grid-column: full; }
Dashboard grid
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: minmax(80px, auto);
.widget-sm { grid-column: span 3; }
.widget-md { grid-column: span 6; }
.widget-lg { grid-column: span 12; }
Masonry-ish (dense)
grid-template-columns:
repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: 8px;
grid-auto-flow: dense;
/* items set grid-row: span N
JS measures height → sets N */
10. Grid vs Flexbox — When to Use Which
/* Use FLEXBOX when: */
/* - One-dimensional: a row of buttons, a horizontal nav, a stack of cards */
/* - Items dictate their own size and you distribute remaining space */
/* - Content-out: "fit as many as will go, then wrap" */
/* - Examples: nav bar, button group, tag list, media object (image + text) */
/* Use GRID when: */
/* - Two-dimensional: rows AND columns matter simultaneously */
/* - Layout-in: you define the layout structure first, then place content into it */
/* - Items need to align across rows (equal-height columns, card grids) */
/* - You need named areas, overlapping, or breakout elements */
/* - Examples: page layout, dashboard, data table, photo gallery, card grid */
/* They compose freely — a grid item can be a flex container and vice versa */
Chapter Summary
| Concept | Key point |
| fr unit | Fraction of remaining space after fixed tracks are placed. Better than % because it accounts for gap. 1fr 2fr = 33% / 67% of available space. |
| minmax() | Sets a min and max for a track. minmax(200px, 1fr) = at least 200px, grow to fill. minmax(min-content, auto) = shrink to content, grow freely. |
| auto-fit vs auto-fill | Both fill as many tracks as fit. auto-fit collapses empty tracks so items grow. auto-fill preserves empty track space. Use auto-fit for most card grids. |
| grid-template-areas | Names regions visually in CSS. Makes page layouts readable at a glance. Implicitly creates named lines (area-start / area-end). Responsive via media query redefinition. |
| grid-column: span N | Place an item without specifying start line — auto-placed but spans N tracks. Combine with auto-fit for responsive span layouts. |
| -1 line number | grid-column: 1 / -1 spans the full explicit grid width. Negative numbers count from the end edge of the explicit grid. |
| Implicit grid | Tracks created automatically for items that overflow the explicit grid. Size them with grid-auto-rows / grid-auto-columns. |
| grid-auto-flow: dense | Backfills gaps with smaller items — changes visual order. Avoid for content with meaningful reading order. |
| subgrid | Child grid inherits parent track definitions. Solves cross-card alignment for titles, bodies, and CTAs. Supported in all modern browsers. |
| place-items: center | The single cleanest way to centre content inside a grid cell. Shorthand for align-items + justify-items. |
Exercises
- Named area layout: Build a full-page layout with header, sidebar nav, main content, and footer using
grid-template-areas. Add a single media query that stacks everything into one column on mobile by redefining the template areas string.
- auto-fit card grid: Create a card grid using
repeat(auto-fit, minmax(240px, 1fr)). Add 3 cards, then 6, then 2. Observe how cards grow when there are fewer than would fill a row. Compare with auto-fill.
- Spanning items: Build a photo gallery grid where the first photo spans 2 columns and 2 rows, and every other photo occupies a single cell. Use
grid-column: span 2; grid-row: span 2 on the featured photo.
- Article breakout: Create an article layout using named lines —
[full-start] and [content-start / content-end] and [full-end]. Make body text constrained to the content column but hero images and pull-quotes break out to full width.
- Subgrid cards: Build a 3-column card grid where each card has an image, title, body text, and CTA button. Without subgrid, observe how mismatched content lengths misalign the CTAs. Then apply
grid-template-rows: subgrid to the cards and fix the alignment.
Next: Chapter 7 — Deep Dive: Responsive Design.
Media queries in depth, container queries, the viewport meta tag,
fluid sizing with clamp() and viewport units, intrinsic layouts
that need no breakpoints, and a modern responsive workflow.