"Grid is a coordinate system. Define the tracks; the items snap into cells. Think in 2D from the start."
The Grid Vocabulary
- Track — a row or a column. A grid has row tracks and column tracks.
- Line — the boundary between (or around) tracks. Lines are numbered starting from 1.
- Cell — the intersection of one row and one column. The smallest unit of placement.
- Area — a rectangle of one or more cells. An item can occupy an area.
- Item — a direct child of the grid container; it occupies one or more cells.
Declaring Tracks
You define tracks with grid-template-columns and grid-template-rows. Each value in the list is one track's size:
grid-template-columns: 100px 200px 300px— three columns of those exact widths.grid-template-columns: 1fr 2fr 1fr— three columns sharing space in 1:2:1 ratio.frmeans "fractional unit of remaining space."grid-template-columns: 240px 1fr— fixed sidebar + flexible main. The classic two-column app shell.grid-template-columns: repeat(3, 1fr)— three equal columns.repeat(N, value)repeats a track definition N times.grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))— as many 200px+ columns as fit, each expanding to fill remaining space. The responsive grid one-liner.
The fr Unit
fr = fractional. It distributes remaining space (after fixed-size tracks are accounted for) in the proportions you specify. 1fr 1fr = 50/50. 2fr 1fr 1fr = 50/25/25. 240px 1fr = sidebar 240px, main gets the rest. fr works on the container's content area; fixed-size tracks subtract first, then fr divides what's left.
The minmax() Function
minmax(100px, 1fr) = "at least 100px, at most 1fr". This is the key to responsive grids — columns stay readable even when the container shrinks, but they expand to fill extra space when the container is wide.
The repeat() Function
repeat(3, 1fr)— exactly three equal tracks.repeat(auto-fill, 200px)— as many 200px tracks as fit; tracks stay 200px wide. Empty tracks remain (reserve space).repeat(auto-fit, 200px)— same, but empty tracks collapse and existing items expand to fill.repeat(auto-fit, minmax(200px, 1fr))— the responsive grid. Cards are at least 200px; if container has room for 4, you get 4 columns; if only 2 fit, the cards expand to fill the row.
auto-fit vs auto-fill: identical when the grid has enough items to fill the tracks. When items are fewer than tracks: auto-fill leaves empty space (preserving the grid shape); auto-fit collapses empty tracks and stretches existing items to fill the row. Almost everyone wants auto-fit for content cards.
Implicit Tracks
If you have more items than the explicit grid has cells, Grid creates additional rows (or columns) automatically. The size of these implicit tracks is controlled by grid-auto-rows / grid-auto-columns. Default is auto (sized by content).
Gap
gap: 1rem on a grid container puts 1rem of space between every row and every column. row-gap: 2rem; column-gap: 1rem sets them independently. Same property name as Flexbox; same semantics.
Placing Items
Items snap into cells automatically in source order (auto-placement, next lesson covers details). To override, use grid-column and grid-row:
grid-column: 1 / 3— span from column line 1 to column line 3 (= two cells).grid-column: 1 / -1— span from line 1 to the last line. The-1always means "the last explicit line." Useful for full-width header in a multi-column grid.grid-column: span 2— span 2 columns, wherever auto-placement puts the item.grid-area: 2 / 1 / 4 / 3— row-start / column-start / row-end / column-end. Four values, full placement.
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) replaces 80% of media queries for card-based layouts. One declaration, infinite responsive behavior.Pippa's Note
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)). On a 1600px monitor it shows 5 quest cards per row; on a 768px tablet, 2-3; on a phone, 1. Zero media queries for the grid itself. The quest content pages use named areas (next lesson) for the sidebar + main + meta layout. Two patterns, dozens of pages.