CSS Variables
🔲 Chapter 9 — CSS Grid
Flexbox is one-dimensional: you work along a single axis — either a row or a column. CSS Grid is two-dimensional: you define columns and rows at the same time, then place items precisely anywhere in that structure. Grid is the tool for page-level layouts — headers, sidebars, main content, footers — and for anything that requires items to align across both axes simultaneously, like galleries, dashboards, and form grids. Flexbox and Grid are complementary; most real pages use both.
1 — Grid vs Flexbox: When to Use Which
- One direction — laying items out in a row or column
- Content-driven — items size themselves; the container adapts
- Components — navbars, button groups, card internals, tag clouds
- Alignment — centring a single item or distributing a group
- Two dimensions — rows and columns at the same time
- Layout-driven — you define the structure, items fit into it
- Page layout — header/sidebar/main/footer arrangements
- Precise placement — items that span multiple rows or columns
2 — Activating Grid
Setting display: grid on an element makes it a grid container. Its direct children become grid items. Without any other properties, the grid has one column and the items stack as rows — identical to normal block flow. Grid only becomes useful when you define columns and rows.
.container {
display: grid; /* block-level grid container */
}
/* Without columns, items stack — same as block layout.
Define columns to unlock Grid's power: */
.three-col {
display: grid;
grid-template-columns: 1fr 1fr 1fr; /* three equal columns */
gap: 16px;
}
/* display: inline-grid — grid container that sits inline */
.inline-grid {
display: inline-grid;
}
3 — The fr Unit
The fr unit stands for fraction — it represents a share of the available space in the grid container after fixed sizes and gaps have been subtracted. It is unique to CSS Grid.
1fr 1fr 1fr— three equal columns, each taking one third of the space.2fr 1fr— two columns where the first is twice as wide as the second.200px 1fr— a fixed 200px column and a flexible column that takes all the rest.1fr 1fr 1fris identical torepeat(3, 1fr)— therepeat()function is shorthand for repeated track sizes.
.grid {
display: grid;
/* These are all equivalent */
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: repeat(3, 1fr);
/* Mixed — fixed sidebar, two flexible columns */
grid-template-columns: 200px 1fr 1fr;
grid-template-columns: 200px repeat(2, 1fr);
/* Define row heights explicitly */
grid-template-rows: 60px 1fr 40px;
gap: 16px; /* same gap in both directions */
gap: 12px 20px; /* row-gap column-gap */
}
4 — Grid Lines and Item Placement
CSS Grid numbers every line between and around columns and rows, starting at 1. A 3-column grid has 4 column lines (1, 2, 3, 4) and a 2-row grid has 3 row lines. Items are placed by specifying which lines they start and end on.
/* Explicit start / end line */
.item {
grid-column: 1 / 3; /* from column line 1 to column line 3 — spans 2 cols */
grid-row: 1 / 2; /* from row line 1 to row line 2 — occupies row 1 */
}
/* span keyword — "span N tracks from wherever this item starts" */
.wide { grid-column: span 2; } /* takes 2 columns */
.full { grid-column: 1 / -1; } /* from line 1 to the last line (-1) */
.tall { grid-row: span 2; } /* occupies 2 rows */
/* Shorthand: grid-area: row-start / col-start / row-end / col-end */
.hero { grid-area: 1 / 1 / 3 / 4; } /* rows 1-3, columns 1-4 */
5 — grid-template-areas
Named template areas let you draw your layout in plain text directly in the CSS. Each string represents a row; each word in the string represents a cell. Repeating a name across cells causes that item to span all of them. A dot (.) represents an empty cell.
.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr 50px;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
gap: 12px;
}
/* Assign each element to its named area */
.site-header { grid-area: header; }
.site-sidebar { grid-area: sidebar; }
.site-main { grid-area: main; }
.site-footer { grid-area: footer; }
/* Responsive — collapse to single column on mobile */
@media (max-width: 640px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
6 — minmax(), auto-fill, and auto-fit
These three tools together enable intrinsically responsive grids — grids that automatically adjust their column count as the container grows or shrinks, with no media queries.
- minmax(min, max) — defines a track that is at least
minwide and at mostmaxwide. - auto-fill — fills the row with as many tracks as will fit, including empty tracks.
- auto-fit — same as auto-fill, but collapses empty tracks to zero width, letting existing items stretch to fill the space.
/* The most powerful single line of layout CSS */
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}
/* → At 1200px wide: fits 4 columns of 240px+
→ At 800px wide: fits 3 columns
→ At 500px wide: fits 2 columns
→ At 240px wide: fits 1 column (mobile)
All without a single media query. */
/* minmax for rows — auto-rows sets implicit row height */
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(100px, auto);
/* rows are at least 100px but grow with content */
gap: 12px;
}
/* auto-fill vs auto-fit — same here because all cells are filled */
/* Difference appears when items don't fill the last row: */
/* auto-fill: empty tracks remain, items stay at minmax width */
/* auto-fit: empty tracks collapse, items stretch to fill row */
7 — The Implicit Grid
When you place more items than fit in the explicit grid (the one you defined with grid-template-columns/rows), or when you place an item outside the defined area, CSS Grid automatically generates additional tracks. These extra tracks form the implicit grid.
- Implicit rows are sized
autoby default — they grow to fit their content. - Use
grid-auto-rowsto set a default height for any automatically-created rows. - Use
grid-auto-columnsfor any automatically-created columns. grid-auto-flowcontrols whether overflow items are placed in new rows (row, default) or new columns (column).
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Only 1 row is explicitly defined — extra items create implicit rows */
grid-auto-rows: 120px; /* all implicit rows are 120px tall */
grid-auto-rows: minmax(80px, auto); /* at least 80px, grows with content */
grid-auto-flow: row; /* default — overflow items go in new rows */
grid-auto-flow: column; /* overflow items go in new columns */
grid-auto-flow: row dense; /* pack items into gaps left by spanning items */
}
grid-auto-flow: dense is particularly useful for masonry-style galleries — it backfills any gaps left by items that span multiple columns or rows, producing a tighter layout without holes.8 — Alignment in Grid
Grid has two sets of alignment properties that mirror Flexbox's names, but apply across two axes:
| Property | Applies to | Controls |
|---|---|---|
justify-items | Container | Aligns all items along the inline (row) axis — left/right within their cell |
align-items | Container | Aligns all items along the block (column) axis — top/bottom within their cell |
justify-self | Item | Overrides justify-items for a single item |
align-self | Item | Overrides align-items for a single item |
justify-content | Container | Distributes columns within the container (when grid is narrower than container) |
align-content | Container | Distributes rows within the container (when grid is shorter than container) |
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Item alignment within cells */
justify-items: stretch; /* default — fills cell width */
justify-items: start; /* left-aligns items in their cell */
justify-items: center; /* centres items horizontally in cell */
justify-items: end; /* right-aligns */
align-items: center; /* vertically centres items in row */
}
/* Per-item overrides */
.item {
justify-self: end; /* right-align this item in its cell */
align-self: end; /* bottom-align this item in its cell */
}
/* Place at dead centre of the grid container */
.single-child {
justify-self: center;
align-self: center;
}
9 — Practical Patterns
Holy Grail page layout
The classic web layout — header, footer, sidebar, and main content — that was notoriously difficult before Grid.
.page {
display: grid;
grid-template-columns: 220px 1fr 180px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
min-height: 100vh;
gap: 20px;
}
header { grid-area: header; }
nav { grid-area: sidebar; }
main { grid-area: main; }
aside { grid-area: aside; }
footer { grid-area: footer; }
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"aside"
"footer";
}
}
Photo gallery with featured image
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 200px;
gap: 8px;
}
.gallery .featured {
grid-column: span 2;
grid-row: span 2;
}
.gallery img {
width: 100%;
height: 100%;
object-fit: cover; /* fills cell without distorting */
}
Responsive card grid — no media queries
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
grid-auto-rows: minmax(160px, auto);
gap: 20px;
}
/* Cards flow from 1 to 4 columns automatically based on available width */
10 — Quick Reference
Container
| Property | Values / example | What it does |
|---|---|---|
display | grid / inline-grid | Activates Grid on the container |
grid-template-columns | 1fr 1fr, 200px 1fr, repeat(3, 1fr) | Defines the explicit column tracks |
grid-template-rows | 60px 1fr auto | Defines the explicit row tracks |
grid-template-areas | "header header" "sidebar main" | Assigns named areas to the grid cells |
gap | length / two lengths | Space between tracks (row-gap column-gap) |
grid-auto-rows | 100px, minmax(80px, auto) | Sets height of implicitly-created rows |
grid-auto-flow | row / column / row dense | Controls auto-placement direction; dense backfills gaps |
justify-items | stretch / start / center / end | Aligns all items along the inline axis within their cells |
align-items | stretch / start / center / end | Aligns all items along the block axis within their cells |
Items
| Property | Values / example | What it does |
|---|---|---|
grid-column | 1 / 3, span 2, 1 / -1 | Sets column start and end lines |
grid-row | 2 / 4, span 2 | Sets row start and end lines |
grid-area | header (named) or 1/1/3/4 (lines) | Assigns item to named area, or sets all four lines at once |
justify-self | start / center / end / stretch | Overrides justify-items for this item |
align-self | start / center / end / stretch | Overrides align-items for this item |
Functions
| Function | Example | What it does |
|---|---|---|
repeat() | repeat(4, 1fr) | Repeats a track size N times |
minmax() | minmax(200px, 1fr) | Sets a minimum and maximum track size |
auto-fit | repeat(auto-fit, minmax(…)) | As many columns as fit; empty tracks collapse |
auto-fill | repeat(auto-fill, minmax(…)) | As many columns as fit; empty tracks are preserved |
✏️ Exercises
Open DevTools alongside each exercise and inspect the grid overlay — in Chrome and Firefox, clicking the grid icon next to a grid container highlights the track lines and areas on the page. This visual feedback is invaluable when learning Grid.
grid-template-areas to assign them. Add a full-width header above and a full-width footer below. All five areas should be visible on screen at once.grid-template-columns: 1fr 3fr 1fr gives the 1:3:1 ratio. Define grid-template-areas across three rows: header, the three-column middle row, and footer. Use grid-area on each child element to place it.<div class="editorial">
<header>Site Header</header>
<nav class="toc">Table of Contents</nav>
<main>Main Article Content</main>
<aside>Related Links</aside>
<footer>Site Footer</footer>
</div>
.editorial {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"toc main aside"
"footer footer footer";
min-height: 100vh;
gap: 16px;
padding: 16px;
background: #0d1117;
}
header { grid-area: header; background: #1a1d27; padding: 16px; border-radius:6px; }
.toc { grid-area: toc; background: #1a1d27; padding: 16px; border-radius:6px; }
main { grid-area: main; background: #1a1d27; padding: 16px; border-radius:6px; }
aside { grid-area: aside; background: #1a1d27; padding: 16px; border-radius:6px; }
footer { grid-area: footer; background: #1a1d27; padding: 16px; border-radius:6px; }
repeat(auto-fit, minmax(220px, 1fr)) does all the work. Use grid-auto-rows: 1fr (or a fixed height) to equalise card heights — or make the cards themselves flex column containers with flex: 1 on the content area to push footers down..product-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
grid-auto-rows: 1fr;
gap: 20px;
}
.product-card {
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius: 8px;
padding: 16px;
display: flex;
flex-direction: column;
font-family: system-ui, sans-serif;
}
.product-card .img-area {
height: 140px;
background: #12141e;
border-radius: 4px;
margin-bottom: 12px;
}
.product-card .desc {
flex: 1; /* push the price to the bottom */
color: #a0a4b8;
font-size: 0.85em;
}
grid-column: span 2; grid-row: span 2; to the first article. All remaining articles auto-place themselves around it. Use grid-auto-rows: 220px for consistent row heights. Set grid-auto-flow: dense to avoid any layout gaps.<div class="magazine">
<article class="lead">
<div class="article-img"></div>
<h2>Lead Story Headline</h2>
<p class="byline">By Staff Reporter</p>
</article>
<!-- Repeat <article> structure for articles 2–6 -->
</div>
.magazine {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 220px;
grid-auto-flow: dense;
gap: 12px;
}
.magazine article {
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius: 6px;
overflow: hidden;
display: flex;
flex-direction: column;
font-family: system-ui, sans-serif;
padding: 12px;
}
/* Lead story spans 2 columns and 2 rows */
.magazine .lead {
grid-column: span 2;
grid-row: span 2;
}
.article-img {
background: #12141e;
flex: 1;
border-radius: 3px;
margin-bottom: 8px;
}
position: fixed from Chapter 7), and below it a grid-based main area with a collapsible-style sidebar (fixed width) and a content region. Inside the content region, use a nested grid to show four stat-card widgets in a 2×2 arrangement, and below those a wide chart placeholder spanning the full content width. The sidebar should list navigation links. Combine everything you have learned from Chapters 6–9.display: grid; grid-template-columns: 220px 1fr;. The content area has its own nested grid: grid-template-columns: 1fr 1fr; for the stat cards, then a grid-column: 1 / -1 element for the full-width chart below.<nav class="top-nav">Dashboard</nav>
<div class="app-layout">
<aside class="sidebar">
<ul>
<li>Overview</li>
<li>Analytics</li>
<li>Reports</li>
<li>Settings</li>
</ul>
</aside>
<main class="content">
<div class="stat-card">Users: 1,204</div>
<div class="stat-card">Revenue: £8,430</div>
<div class="stat-card">Sessions: 23,891</div>
<div class="stat-card">Bounce rate: 34%</div>
<div class="chart-area">Chart placeholder</div>
</main>
</div>
/* Fixed top nav */
.top-nav {
position: fixed;
top: 0; left: 0; right: 0;
height: 52px;
background:#12141e;
border-bottom:1px solid #2a2d3a;
z-index: 100;
display: flex;
align-items:center;
padding: 0 20px;
font-weight:700;
font-family:system-ui, sans-serif;
color: #4f8ef7;
}
/* Grid-based app shell */
.app-layout {
display: grid;
grid-template-columns: 220px 1fr;
min-height: 100vh;
margin-top: 52px; /* offset for fixed nav */
}
.sidebar {
background: #12141e;
border-right:1px solid #2a2d3a;
padding: 20px;
font-family: system-ui, sans-serif;
}
/* Nested grid for the content area */
.content {
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-rows: minmax(100px, auto);
gap: 16px;
padding: 20px;
align-content: start;
}
.stat-card {
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius:6px;
padding: 20px;
font-family: system-ui, sans-serif;
font-weight: 700;
color: #e2e4ec;
}
.chart-area {
grid-column: 1 / -1; /* span full content width */
background: #1a1d27;
border: 1px solid #2a2d3a;
border-radius:6px;
padding: 20px;
min-height: 200px;
font-family: system-ui, sans-serif;
color: #7a7f96;
display: flex;
align-items: center;
justify-content:center;
font-style: italic;
}