Grid Fundamentals
🔲 Chapter 3 — Grid Fundamentals
Flexbox is one-dimensional — it lays items out in a row or a column. CSS Grid is two-dimensional — it controls both rows and columns at the same time. Grid is how you build page layouts, dashboard panels, image galleries, and anything that requires items to be aligned in both directions simultaneously. This chapter covers the foundational Grid concepts: defining tracks, the fr unit, auto placement, explicit item placement, named areas, and alignment.
1 — Grid vs Flexbox: When to Use Which
Grid and Flexbox are complementary, not competing. A common rule of thumb:
2 — Creating a Grid Container
Like Flexbox, Grid is activated with a single declaration on the parent element. Direct children automatically become grid items.
/* The container becomes a grid context */
.container {
display: grid;
}
/* Without grid-template-columns, all items stack in one column */
/* That is the implicit grid's default behaviour */
display: inline-grid also works — the container becomes inline-level (shrinks to content width) but its children still use grid layout.Two key terms you need before going further:
- Grid track — a single row or column in the grid (the space between two grid lines).
- Grid cell — the intersection of a row track and a column track. The smallest addressable unit in a grid.
- Grid area — one or more grid cells that an item occupies (can span multiple rows and/or columns).
- Grid lines — the dividing lines between tracks. Columns have vertical lines numbered left-to-right from 1. Rows have horizontal lines numbered top-to-bottom from 1. Lines can also be numbered from the end using negative numbers (-1 is the last line).
3 — grid-template-columns and grid-template-rows
These two properties define the explicit grid — the tracks you declare up front. Each space-separated value creates one track of that size.
.grid {
display: grid;
/* Three fixed-width columns */
grid-template-columns: 200px 200px 200px;
/* Same thing using repeat() */
grid-template-columns: repeat(3, 200px);
/* Three equal flexible columns */
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: repeat(3, 1fr);
/* Fixed sidebar + flexible main */
grid-template-columns: 240px 1fr;
/* Two explicit rows: 60px header, flexible content */
grid-template-rows: 60px 1fr;
}
repeat(4, 1fr) is identical to 1fr 1fr 1fr 1fr — it creates four equal-width tracks. You can also mix: repeat(3, 1fr) 200px creates three flexible columns plus one fixed column.
4 — The fr Unit: Fractions of Available Space
fr stands for fraction. One fr represents one share of the space left over after fixed-size tracks have claimed their portion. It works like flex-grow but for grid tracks.
.grid {
display: grid;
width: 900px;
/* Fixed column takes 200px, remaining 700px split 1:2 */
grid-template-columns: 200px 1fr 2fr;
/* ↑ ↑ ↑
200px 233px 467px
(700px ÷ 3 units × 1 = 233px)
(700px ÷ 3 units × 2 = 467px) */
}
/* fr tracks expand to fill the container. */
/* Unlike %, they automatically account for gap spacing. */
fr units respect gap — they distribute space after gaps are subtracted. Using percentages with gaps requires manual calculation. Always prefer fr for flexible tracks.gap: 20px and use percentages, you need to calculate: calc((100% - 40px) / 3) for each column. With fr, you just write 1fr 1fr 1fr and the browser handles the gap subtraction automatically.
5 — gap: Space Between Tracks
gap works identically in Grid as in Flexbox — it adds space between cells but not at the outer edges. In Grid you can set row and column gaps independently.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px; /* same row and column gap */
gap: 16px 24px; /* row-gap column-gap */
row-gap: 16px; /* vertical space between rows */
column-gap: 24px; /* horizontal space between columns */
}
6 — The Implicit Grid and Auto Placement
When you have more items than your defined tracks can hold, the browser creates new rows automatically. These extra rows form the implicit grid. You can control the size of these auto-created rows with grid-auto-rows.
more content
tall content here
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
/* All auto-created rows: at least 120px, can grow */
grid-auto-rows: minmax(120px, auto);
}
/* grid-auto-flow controls whether new items go across (row)
or down (column) when they overflow the explicit grid */
.grid {
grid-auto-flow: row; /* default: fill rows first */
grid-auto-flow: column; /* fill columns first instead */
grid-auto-flow: dense; /* back-fill gaps left by spanning items */
}
minmax(min, max) defines a track that is at least min and at most max. minmax(120px, auto) means: at least 120px tall, but grow if content is taller. This is perfect for card grids where content length varies.7 — Grid Lines: Placing Items Explicitly
Every track boundary is a numbered grid line. Columns have vertical lines numbered 1, 2, 3… from left to right. Rows have horizontal lines numbered 1, 2, 3… from top to bottom. You can also address lines from the end using negative numbers: -1 is the last line, -2 is the second to last, and so on.
Use grid-column and grid-row on items to place them at specific line intersections, or use span to make an item cover multiple tracks.
/* Line-number syntax: start-line / end-line */
.header {
grid-column: 1 / 4; /* from line 1 to line 4 = full width (3-col grid) */
grid-column: 1 / -1; /* same — -1 always means the last line */
}
/* Span keyword: "start here, cross N tracks" */
.wide-card {
grid-column: span 2; /* 2 columns wide, auto-placed */
}
.featured {
grid-column: span 2;
grid-row: span 2; /* 2 columns wide AND 2 rows tall */
}
/* grid-area shorthand: row-start / col-start / row-end / col-end */
.item {
grid-area: 1 / 2 / 3 / 4; /* rows 1–3, columns 2–4 */
}
8 — grid-template-areas: Naming Regions
grid-template-areas lets you draw your layout as ASCII art in your CSS. Each quoted string represents a row; each word in the string is a named zone. Use a dot (.) for an empty cell. Items then reference their zone by name with grid-area.
.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr 48px;
min-height: 100vh;
gap: 0;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
/* Each direct child references its zone by name */
header { grid-area: header; }
.sidebar{ grid-area: sidebar; }
main { grid-area: main; }
footer { grid-area: footer; }
grid-template-areas implicitly define named grid lines too — "header" creates lines named header-start and header-end on both axes.@media (max-width: 640px) {
.page {
grid-template-columns: 1fr; /* single column */
grid-template-rows: auto; /* all rows auto-sized */
grid-template-areas:
"header"
"main"
"sidebar"
"footer"; /* sidebar moves below main */
}
}
grid-template-areas in a media query completely reorganises the layout with no HTML changes. This is the killer feature of named areas.grid-template-areas must occupy a contiguous rectangular region — no L-shapes or disconnected cells. An invalid shape causes the entire declaration to be ignored. Use a dot (.) for intentionally empty cells.
9 — Alignment in Grid
Grid has six alignment properties — three on the container and two on items — plus a distinction between aligning items within their cells vs aligning the grid tracks within the container.
| Property | Set on | Controls | Axis |
|---|---|---|---|
justify-items | Container | Items within their cells (inline = horizontal) | Inline (horizontal) |
align-items | Container | Items within their cells (block = vertical) | Block (vertical) |
justify-content | Container | Grid tracks within the container (if tracks don't fill it) | Inline (horizontal) |
align-content | Container | Grid tracks within the container | Block (vertical) |
justify-self | Item | That item within its cell (overrides justify-items) | Inline (horizontal) |
align-self | Item | That item within its cell (overrides align-items) | Block (vertical) |
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Items fill their cells (default for both) */
justify-items: stretch; /* default */
align-items: stretch; /* default */
/* Centre items within their cells */
justify-items: center;
align-items: center;
}
/* Centre a specific item within its cell */
.special {
justify-self: center;
align-self: end;
}
/* Centre the entire grid inside the container */
/* (only visible when tracks are smaller than the container) */
.grid-centered {
justify-content: center;
align-content: center;
}
place-items: center and place-self: center set both the inline and block axes at once — they are equivalent to writing align-items: center; justify-items: center and save a line of code when both values are the same.
10 — minmax(): Flexible Track Bounds
minmax(min, max) defines a track that is at least min wide/tall and at most max — the browser picks a size within that range. It unlocks truly responsive track sizing without media queries.
/* ── In grid-template-columns ── */
.grid {
/* Column at least 200px, can grow to 1fr */
grid-template-columns: minmax(200px, 1fr) 1fr;
/* Fixed sidebar, content never narrower than 300px */
grid-template-columns: 240px minmax(300px, 1fr);
}
/* ── In grid-auto-rows ── */
.card-grid {
grid-template-columns: repeat(3, 1fr);
/* Rows: at least 120px, but expand for taller content */
grid-auto-rows: minmax(120px, auto);
}
/* ── Common special values ── */
/* minmax(0, 1fr) — like 1fr but can shrink to zero (avoids overflow) */
/* minmax(min-content, 1fr) — at least as wide as the content */
/* minmax(200px, max-content) — at least 200px, grows to fit content */
minmax(0, 1fr) often in advanced grids. The default min of an fr track is auto (content size), which can cause overflow. Setting the min to 0 removes that lower bound.11 — Quick Reference
Container properties
| Property | What it defines |
|---|---|
display: grid | Creates a grid formatting context; direct children become grid items |
grid-template-columns | Explicit column tracks (sizes, fr units, repeat()) |
grid-template-rows | Explicit row tracks |
grid-template-areas | Named region map — draw the layout as ASCII strings |
gap / row-gap / column-gap | Space between tracks (not at edges) |
grid-auto-rows | Size for implicitly created rows (overflow items) |
grid-auto-columns | Size for implicitly created columns |
grid-auto-flow | How auto-placed items fill the grid: row, column, dense |
justify-items / align-items | Default alignment of all items within their cells |
justify-content / align-content | Distribution of tracks within the container |
Item properties
| Property | What it controls |
|---|---|
grid-column: s / e | Start and end column lines (or span N) |
grid-row: s / e | Start and end row lines (or span N) |
grid-area: name | Assigns item to a named area from grid-template-areas |
grid-area: r/c/re/ce | Shorthand for row-start / col-start / row-end / col-end |
justify-self / align-self | Per-item alignment within its cell |
Key functions
| Function | Usage |
|---|---|
repeat(n, size) | Create n tracks of size. Avoids repetition: repeat(4, 1fr) |
minmax(min, max) | Track is at least min, at most max. E.g. minmax(120px, auto) |
✏️ Exercises
Build each layout in a fresh HTML file. Try to write the CSS before peeking at the solution.
grid-auto-rows and minmax().repeat(3, 1fr) for equal columns. grid-auto-rows: minmax(120px, auto) for the row height..card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(120px, auto);
gap: 20px;
}
.card {
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius: 8px;
padding: 20px;
}
grid-template-areas with four zones: header (full width, 60px tall), sidebar (200px wide, fills remaining height), main (flexible, fills remaining space), and footer (full width, 48px tall). The page should fill at least the full viewport height.200px 1fr) and a 3-row template (60px 1fr 48px). Define three area strings. Each HTML element gets grid-area: zonename.<div class="page">
<header>Site Header</header>
<aside class="sidebar">Sidebar</aside>
<main>Main Content</main>
<footer>Footer</footer>
</div>
.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr 48px;
min-height: 100vh;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
header { grid-area: header; background: #1e2235; }
.sidebar { grid-area: sidebar; background: #12141e; }
main { grid-area: main; padding: 24px; }
footer { grid-area: footer; background: #1e2235; }
minmax(180px, auto).repeat(4, 1fr) for columns. Give the first item grid-column: 1 / -1 (or span 4). The rest need no explicit placement..editorial {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(180px, auto);
gap: 16px;
}
.editorial .hero {
grid-column: 1 / -1; /* spans all 4 columns */
background: #1a2a4a;
border-radius: 8px;
}
.editorial .card {
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius: 8px;
padding: 16px;
}
grid-template-areas inside a media query — no HTML changes.grid-template-columns: 1fr and rewrite grid-template-areas with four single-cell rows. You can remove grid-template-rows and let it go auto.@media (max-width: 700px) {
.page {
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
The sidebar appears below the main content on mobile because of its new position in the area map — no order property needed, no DOM reordering, just a redrawn template.