Two Dimensions, Solved
Grid is the first CSS layout system designed for layout rather than adapted into it, and it makes a page skeleton about four lines long. Draw columns, span a cell across two of them, and meet the unit that only exists here.
Worth reading first: One Dimension, Solved
Grid is the first CSS layout system designed for layout rather than adapted into it, and it makes a page skeleton about four lines long. Draw columns, span a cell across two of them, and meet the unit that only exists here.
Grid defines the tracks, and then fills them
Flexbox arranges items and lets their content decide the sizes. Grid works the other way around: you define the tracks — the columns and rows — first, and items are placed into the cells they make.
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem;}No classes on the children, no widths, no wrapper elements. The container defines the shape and the items fall into it in order.
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
Three equal columns. fr means "one share of what is left after gaps and fixed tracks are taken out" — which is why this never overflows, and why three 33.3% columns with a gap do.
Rows are usually left implicit — grid-template-rows exists and you rarely need it, because the browser creates as many rows as the items require and sizes them to their content.
The fr unit divides what is left over
fr is a fraction of the available space, and it exists only in grid. It is the piece that makes the whole system pleasant.
grid-template-columns: 1fr 1fr; /* two equal columns */grid-template-columns: 2fr 1fr; /* first twice as wide as second */grid-template-columns: 200px 1fr; /* fixed sidebar, flexible main */grid-template-columns: 200px 1fr 200px; /* two sidebars */The important difference from percentages: fr divides what is left after gaps and fixed tracks are taken out. Percentages divide the whole container and know nothing about the gap.
Percentages overflow
Three columns at 33.333% with a 1rem gap adds up to 100% plus two gaps — so the row is wider than its container and the page scrolls sideways. Fixing it means calc() and arithmetic that changes whenever the gap does.
fr just fits
1fr 1fr 1fr with any gap always fits. The gaps come out first and the remainder is split three ways. Change the gap and nothing else needs touching.
Mixing units in one definition is normal and useful:
grid-template-columns: auto 1fr auto;/* first and last sized to their content, middle takes the rest — an icon, a label, and a button in a row */Spanning cells is what flexbox cannot do
A flex item can grow and shrink. It cannot occupy two columns while its siblings stay aligned in the grid, because in flexbox there is no grid for them to stay aligned to.
.featured { grid-column: span 2; /* two columns wide */} .tall { grid-row: span 2; /* two rows tall */} .hero { grid-column: 1 / -1; /* first line to last: full width */}grid-column: 1 / -1 is worth remembering. Grid numbers the lines between tracks rather than the tracks themselves, and negative numbers count from the end — so -1 is always the final line whatever the column count. That makes it a full-width row that survives changing the grid.
.page { display: grid; grid-template-columns: 200px 1fr; grid-template-areas: "header header" "sidebar main" "footer footer"; gap: 1rem;} .page > header { grid-area: header; }.page > aside { grid-area: sidebar; }.page > main { grid-area: main; }.page > footer { grid-area: footer; }Rearranging the whole page is then editing those three strings. Note that this changes only the visual order — the DOM order is what a screen reader and the tab key follow, so do not use it to reorder anything where sequence carries meaning.
repeat and minmax remove the media query
Two functions, and together they produce the most useful single line in modern CSS.
grid-template-columns: repeat(3, 1fr); /* same as 1fr 1fr 1fr */grid-template-columns: repeat(4, minmax(0, 1fr)); /* four, and they may shrink */ /* The line worth memorising */.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem;}Read it as: as many columns as fit, each at least 250 pixels, sharing any leftover space equally. On a phone that is one column. On a tablet, two. On a wide monitor, five. There is no media query and no breakpoint to choose.
Repeat a track definition n times. Purely a shorthand.
As many as fit, and COLLAPSE any empty tracks so the existing items stretch to fill.
As many as fit, and KEEP empty tracks, so three items in a five-column space stay at their size. Usually auto-fit is what you want.
At least a, at most b. minmax(250px, 1fr) means never narrower than 250 and otherwise share the space.
Grid for the page, flex for the parts
The two are not competitors and real layouts use both, usually nested. The question is how many dimensions you are controlling.
Reach for grid when
You need rows and columns to line up with each other. Page skeletons, card grids, image galleries, forms with aligned labels, anything that should look like a grid.
Reach for flex when
One direction, sized by content. Nav bars, button rows, a card's internals, an icon beside a label, anything that should wrap naturally rather than snap to tracks.
/* Grid places the cards */.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem;} /* Flex arranges the inside of each one */.card { display: flex; flex-direction: column; gap: 0.75rem;} /* And this pushes the button to the bottom, so every card's button lines up regardless of how long its text is. */.card .btn { margin-top: auto;}That last rule is a small piece of craft worth keeping. In a column flex container, margin-top: auto absorbs all the spare vertical space and pins the element to the bottom — so a row of cards with different amounts of text still has its buttons on one line.
Key takeaways
- Grid defines tracks first and places items into the cells; flexbox arranges items and lets content decide sizes.
- Rows are usually implicit — the browser makes as many as the items need.
- fr divides what is left after gaps and fixed tracks, which is why 1fr 1fr 1fr always fits and three 33.3% columns do not.
- Mixing units — auto 1fr auto — gives content-sized ends and a flexible middle.
- grid-column: span 2 spans columns; 1 / -1 is full width whatever the column count.
- Grid numbers the lines between tracks, and negative numbers count from the end.
- grid-template-areas lets you draw a page skeleton as text, but it changes visual order only — the DOM order is what keyboards and screen readers follow.
- repeat(auto-fit, minmax(250px, 1fr)) is a fully responsive grid in one line with no media query.
- auto-fit collapses empty tracks; auto-fill keeps them.
- minmax(0, 1fr) instead of 1fr stops a long word or wide image from blowing out the grid.
- Grid for the page, flex for the parts, and nest them — that is how real layouts are built.
- In a column flex container, margin-top: auto pins an element to the bottom so buttons line up across cards.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.