Topic 8.1
Grid Basics
In one line
display: grid turns an element into a grid container; grid-template-columns and grid-template-rows draw its tracks, the fr unit shares out leftover space, and gap puts gutters between tracks, while the children fill the cells in order.
Think of it like this
An egg tray. The tray has fixed rows and columns of cups before any egg arrives. You put eggs in one by one, left to right, row by row, and each egg lands neatly in a cup. You never measure where an egg goes: the tray decides. A CSS grid is that tray: you draw the rows and columns on the parent, and the children drop into the cups.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Grid container
- The element with
display: grid. It owns the rows and columns. - Grid item
- A direct child of the grid container. It is placed into the grid's cells.
- Grid line
- An invisible dividing line between tracks, numbered from 1. Three columns have four lines.
- Track
- The space between two neighbouring grid lines: one column or one row.
- Cell
- The box where one row track crosses one column track.
- Grid area
- A rectangle made of one or more cells, which an item can fill.
- `fr` unit
- A share of the free space left after fixed sizes and gaps are taken away.
- Free space
- The container's size minus all fixed tracks, content-sized tracks and gaps.
- `gap`
- The gutter between rows and between columns. Never added around the outside.
- Auto-placement
- The browser putting items into the next empty cell, in order, when you don't place them yourself.
Step by step
01Turn on grid
Six boxes inside a .grid parent. Add display: grid to the parent. The boxes still stack, because a grid with no column template has a single column. But they are now grid items: gaps and tracks will apply to them.
.grid {
display: grid;
}02Draw three columns
List one size per column. Now the six boxes flow into two rows of three: auto-placement fills row 1 left to right, then starts row 2.
.grid {
display: grid;
grid-template-columns: 100px 1fr 2fr;
}03See the lines, tracks and cells
Three column tracks need four column lines. Two rows need three row lines. Each crossing of a row and a column is a cell. DevTools draws these lines for you: click the small grid badge next to the element in the Elements panel.
04Add gutters with gap
gap: 10px puts 10px between columns and between rows, but not around the outside. The gaps come out of the space before fr is shared, so the fr columns get a little narrower.
.grid {
display: grid;
grid-template-columns: 100px 1fr 2fr;
gap: 10px;
}05Work out the fr sizes by hand
Container 420px. Take away the fixed column (100px) and the two gaps (20px): 300px of free space. The fr numbers add up to 3, so one fr is 100px. Column 2 is 1fr = 100px, column 3 is 2fr = 200px. The console in the first example prints exactly that.
06Size the rows too
grid-template-rows: 60px auto makes the first row 60px tall and the second as tall as its tallest item. Without it, every row is auto.
.grid {
display: grid;
grid-template-columns: 100px 1fr 2fr;
grid-template-rows: 60px auto;
gap: 10px;
}Try it yourself
- 1
Predict the fr sizes
In the first example, change the columns to
120px 1fr 1frand keepgap: 10px. Work it out first: 420 − 120 − 20 = 280px of free space, so each1fris 140px. Run and check the console. - 2
Make the percentages fit
In the second example, change
.pcttocalc(50% - 10px) calc(50% - 10px). Predict the right edge (400). That's the arithmeticfrdoes for you, which is whyfris the usual choice. - 3
Cause and cure a blowout
In the third example, change
.widetowidth: 380px. Predict the.acolumns (380px 20px) and the.bcolumns (still200px 200px). Then addoverflow: hiddento.grid > divand see the content get clipped inside its fair column in.b.
Code & diagrams
`getComputedStyle` returns the **resolved** column sizes in px, so `1fr 2fr` comes back as `100px 200px`. Change `gap` to `30px` and work out the new sizes before you run.
Preview
Console
Nothing logged yet.Expected console output
columns: 100px 100px 200px
gaps: 10px 10px
item 1 width: 100
item 2 width: 100
item 3 width: 200
item 4 is on a new row: trueThe dashed outline is the 400px container. With percentages, the second column ends at 420px: it sticks out by exactly one gap. `fr` shares what is left after the gap, so it fits.
Preview
Console
Nothing logged yet.Expected console output
.pct column width: 200 right edge: 420
.fr column width: 190 right edge: 400In `.a` the first column refuses to shrink below its content (300px), so the second gets only 100px. In `.b` the columns stay equal and the green content overflows into column 2.
Preview
Console
Nothing logged yet.Expected console output
.a 300px 100px
.b 200px 200pxBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Put display: grid on the wrong element
In the first example, move display: grid and grid-template-columns from .grid to .grid > div.
Break #2
Use percentages with a gap
Write grid-template-columns: 25% 25% 25% 25%; gap: 16px; on a full-width grid.
Myth vs fact
Myth
display: grid affects every element inside the container.
Fact
Only the direct children become grid items. Grandchildren are laid out normally inside their parent item, unless that item is itself a grid (or a subgrid).
Myth
1fr always means equal columns.
Fact
1fr is minmax(auto, 1fr): a column never shrinks below its content's minimum size. Wide content makes its column bigger. Use minmax(0, 1fr) when columns must stay equal.
Myth
Gaps are just margins on the items.
Fact
Gaps belong to the grid, not the items. They appear only between tracks, never at the outer edges, and they don't collapse or add up the way margins do.
Myth
Grid is only for whole-page layouts.
Fact
Grid is just as good for small components: a form with label and input columns, a card's icon-and-text row, a calendar, a keypad.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The track sizing algorithm runs in stages: fixed tracks get their size, then intrinsic tracks (
auto,min-content,max-content) are sized from their items' contributions, thenfrtracks share the free space, and finallyautotracks are stretched ifjustify-content/align-contentisnormalorstretch. Anfrtrack whose share would be smaller than its base size is frozen at that base size and the share is recomputed, which is exactly the blowout in the third example. - ▸
getComputedStyle(el).gridTemplateColumnsreturns the used track sizes in px, including implicit tracks, not what you wrote. To read what you wrote, look at the stylesheet rule (el.styleor the CSSOM) or DevTools' Styles pane. - ▸
Grid items are blockified: an
<a>or<span>child gets a block-leveldisplay,floatis ignored on them,vertical-alignhas no effect, and margins between items don't collapse. Text directly inside the container is wrapped in an anonymous grid item, which you can't select or style. - ▸
Firefox's Grid Inspector and Chrome's grid overlay (the
gridbadge in Elements, and the Layout pane) draw line numbers, track sizes and area names on the page. They're the fastest way to debug any grid in this phase.
Remember this
- 1
display: gridmakes an element a grid container. Its direct children (not grandchildren) become grid items. Nothing visible changes until you describe the columns: with no column template, a grid has one column, so the items still stack, now with grid rules applied.display: inline-griddoes the same but the container itself sits in a line of text like an inline-block. Both values are about the container; the items are always laid out the grid way. - 2
A grid is made of lines, and the space between two neighbouring lines is a track: a column track runs top to bottom, a row track runs left to right. Where one row crosses one column you get a cell, the smallest piece of the grid. A rectangle of several cells is a grid area. Lines are numbered from 1: a three-column grid has four column lines, 1 to 4. You'll place items by these numbers in Topic 8.3.
- 3
grid-template-columnslists the width of each column track, separated by spaces:grid-template-columns: 200px 1fr 2frmakes three columns.grid-template-rowsdoes the same for rows:grid-template-rows: 80px auto. Every value in the list is one track. You can use any length (px,rem,%),auto(sized by the content, and it also grows to soak up spare space), and the grid's own unit,fr. - 4
**
frmeans "a fraction of the leftover space**". The browser first takes away fixed tracks (200px), content-sized tracks (auto) and the gaps from the container's width. What remains is the free space, split between thefrtracks in proportion to their numbers. In a 420px grid with100px 1fr 2frand two 10px gaps, the free space is 420 − 100 − 20 = 300px, so1fris 100px and2fris 200px. Percentages don't work this way:50% 50%is half of the container each, and the gap is added on top, so the grid overflows. - 5
gapsets the gutters between tracks:gap: 16pxfor both directions, orgap: 12px 24pxfor row gap then column gap (longhandsrow-gapandcolumn-gap). Gaps go only between tracks, never around the outside edge, so you don't need the "remove the last margin" tricks of older layouts. The old namegrid-gapstill works as an alias. - 6
Children fill the grid by auto-placement: the first item goes into row 1 column 1, the next into column 2, and so on; when a row is full, the next item starts a new row. If there are more items than the rows you defined, the grid creates extra rows by itself (the implicit grid, Topic 8.2). Grid items behave differently from normal blocks: their margins never collapse with each other,
floaton an item is ignored, and inline children (like a bare<span>or text) become block-level items. - 7
One subtle rule causes most "my grid is too wide" bugs:
1fris reallyminmax(auto, 1fr). Its minimum isauto, which for a grid item means "at least as wide as my content can shrink to". A long URL, a wide image or a<pre>block makes its column grow past its fair share and squeezes the others. Writingminmax(0, 1fr)removes that minimum, so columns stay equal and the content overflows or wraps instead.
Explain it without notes
What happens to an element and its children when you set display: grid?
How does the browser calculate the size of an fr track?
Why does 50% 50% with a gap overflow, while 1fr 1fr doesn't?
Why can a 1fr column end up much wider than its neighbours, and how do you stop it?
Practice
Make a grid with a fixed 240px sidebar column and a main column that takes the rest, with a 24px gap.
A 600px-wide grid has grid-template-columns: 1fr 2fr 1fr and gap: 20px. How wide is each column?
Build a 3 by 2 grid of equal boxes where each row is exactly 100px tall and the gaps are 12px.
Trade-offs
- ↔
frcolumns adapt to the container but can grow to fit wide content;minmax(0, 1fr)keeps them equal but makes overflow your job (wrap, scroll or clip). - ↔
Fixed
pxtracks are predictable but don't adapt to narrow screens; combine them withfror the functions in Topic 8.2 so at least one track can flex.
Done when you can
I can turn an element into a grid and say which elements become grid items.
I can name grid lines, tracks, cells and areas, and count the lines of a grid.
I can calculate
frsizes by hand from the container width, fixed tracks and gaps.I know why
%tracks plusgapoverflow andfrtracks don't.I can fix a grid blowout with
minmax(0, 1fr).