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:

Use Flexbox when…
Layout flows in one direction
Navigation links, button groups, card interiors, tag lists — anything where items line up in a single row or column and you mainly care about how they distribute along that line.
Use Grid when…
Layout needs rows AND columns
Page layouts, galleries, dashboards, form grids — anything where items need to align in two dimensions and you want precise control over both axes at once.
In practice, you use both. A page uses Grid for the outer structure (header / sidebar / main / footer). Inside each section, Flexbox handles the one-dimensional components — a navbar, a row of cards, a form row. Nesting them is completely normal and expected.

2 — Creating a Grid Container

Like Flexbox, Grid is activated with a single declaration on the parent element. Direct children automatically become grid items.

🎨 CSS — display: grid
/* 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.

Live demo — grid-template-columns (6 items)
1
2
3
4
5
6
grid-template-columns: 1fr;
🎨 CSS — Defining column and row tracks
.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(count, size) is a shorthand that saves typing. 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.

🎨 CSS — How fr is calculated
.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.
fr vs % — why fr wins. If you have three columns with 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.

🎨 CSS — gap in a grid
.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.

Live demo — grid-auto-rows (3-column grid, 9 items, rows auto-created)
1
2
more content
3
4
5
6
tall content here
7
8
9
grid-auto-rows: auto;
🎨 CSS — Controlling the implicit grid
.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.

Grid line numbers on a 3-column, 2-row grid
col 1col 2col 3col 4 / -1
123/-1
cell
cell
cell
cell
cell
cell
● Column lines (vertical) — numbered left → right ● Row lines (horizontal) — numbered top → bottom

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.

Live demo — explicit placement (3-column grid)
1
2
3
4
5
/* all items: auto placement (no explicit grid-column/row) */
🎨 CSS — Placement with line numbers and span
/* 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.

Live demo — classic page layout with named areas
header
sidebar
main
🎨 CSS — The layout above in code
.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; }
The names in grid-template-areas implicitly define named grid lines too — "header" creates lines named header-start and header-end on both axes.
🎨 CSS — Responsive: sidebar below main on mobile
@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 */ } }
Redefining grid-template-areas in a media query completely reorganises the layout with no HTML changes. This is the killer feature of named areas.
⚠️ Area names must form rectangles. Every name in 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.

PropertySet onControlsAxis
justify-itemsContainerItems within their cells (inline = horizontal)Inline (horizontal)
align-itemsContainerItems within their cells (block = vertical)Block (vertical)
justify-contentContainerGrid tracks within the container (if tracks don't fill it)Inline (horizontal)
align-contentContainerGrid tracks within the containerBlock (vertical)
justify-selfItemThat item within its cell (overrides justify-items)Inline (horizontal)
align-selfItemThat item within its cell (overrides align-items)Block (vertical)
🎨 CSS — Common alignment patterns
.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 and place-self. The shorthands 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.

🎨 CSS — minmax() examples
/* ── 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 */
You will see 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

PropertyWhat it defines
display: gridCreates a grid formatting context; direct children become grid items
grid-template-columnsExplicit column tracks (sizes, fr units, repeat())
grid-template-rowsExplicit row tracks
grid-template-areasNamed region map — draw the layout as ASCII strings
gap / row-gap / column-gapSpace between tracks (not at edges)
grid-auto-rowsSize for implicitly created rows (overflow items)
grid-auto-columnsSize for implicitly created columns
grid-auto-flowHow auto-placed items fill the grid: row, column, dense
justify-items / align-itemsDefault alignment of all items within their cells
justify-content / align-contentDistribution of tracks within the container

Item properties

PropertyWhat it controls
grid-column: s / eStart and end column lines (or span N)
grid-row: s / eStart and end row lines (or span N)
grid-area: nameAssigns item to a named area from grid-template-areas
grid-area: r/c/re/ceShorthand for row-start / col-start / row-end / col-end
justify-self / align-selfPer-item alignment within its cell

Key functions

FunctionUsage
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.

Exercise 1
Create a 3-column card grid. The grid should have 9 cards. All columns should be equal width. There should be a 20px gap between cards. The cards should have a fixed minimum height of 120px but grow taller if content requires it. Use grid-auto-rows and minmax().
Hint: repeat(3, 1fr) for equal columns. grid-auto-rows: minmax(120px, auto) for the row height.
CSS
.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; }
Exercise 2
Build a full-page layout using 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.
Hint: Use a 2-column template (200px 1fr) and a 3-row template (60px 1fr 48px). Define three area strings. Each HTML element gets grid-area: zonename.
HTML
<div class="page"> <header>Site Header</header> <aside class="sidebar">Sidebar</aside> <main>Main Content</main> <footer>Footer</footer> </div>
CSS
.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; }
Exercise 3
Create an editorial grid with 12 items on a 4-column grid. The first item should span all 4 columns (a featured hero card). The next three items should each span 1 column. The remaining 8 items fill in normally with auto placement. Use a fixed row height of minmax(180px, auto).
Hint: Set repeat(4, 1fr) for columns. Give the first item grid-column: 1 / -1 (or span 4). The rest need no explicit placement.
CSS
.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; }
Exercise 4
Take the page layout from Exercise 2 and make it responsive: on screens narrower than 700px, collapse to a single column with the order: header → main → sidebar → footer. Redefine grid-template-areas inside a media query — no HTML changes.
Hint: In the media query, set 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.
CSS — add to the solution from Exercise 2
@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.