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

Use Flexbox when…
  • 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
Use CSS Grid when…
  • 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
💡 The practical rule of thumb: use Grid for the page skeleton and major layout regions; use Flexbox inside those regions for the components within them. Nesting both is not just allowed — it is the standard modern approach.

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.

🎨 CSS — Activating Grid and defining columns
.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 1fr is identical to repeat(3, 1fr) — the repeat() function is shorthand for repeated track sizes.
Live demo — three equal 1fr columns
1fr
1fr
1fr
Mixed — 200px fixed + 1fr + 1fr flexible
200px
1fr
1fr
🎨 CSS — fr and repeat()
.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.

col 1
col 2
col 3
col 4
row 1
row 2
row 3
cell 1
cell 2
cell 3
cell 4
cell 5
cell 6
Blue numbers = column lines (1–4). Green numbers = row lines (1–3). Items span between lines.
🎨 CSS — Placing items with grid-column and grid-row
/* 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 */
Live demo — items spanning multiple columns
A — span 2
B
C
D — span 2
E — span 3 (full row: 1 / -1)

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.

Live demo — classic page layout using named grid areas
header
sidebar
main
🎨 CSS — grid-template-areas
.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"; } }
Template area names must form valid rectangles — you cannot make an L-shape or a T-shape with a single name. Each name must appear as a complete rectangle of cells. If the rectangle would be incomplete, the grid is invalid and items fall back to auto-placement.
grid-template-areas is the most readable layout tool in CSS. When you look at the CSS, you can literally see the page structure as ASCII art. This makes it excellent for communicating intent and for responsive layout changes — just redefine the areas string inside a media query and the whole layout reflows.

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 min wide and at most max wide.
  • 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.
Live demo — repeat(auto-fit, minmax(120px, 1fr)) — resize the window to see columns adjust
Alpha
Bravo
Charlie
Delta
Echo
Foxtrot
🎨 CSS — The responsive grid pattern
/* 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 auto by default — they grow to fit their content.
  • Use grid-auto-rows to set a default height for any automatically-created rows.
  • Use grid-auto-columns for any automatically-created columns.
  • grid-auto-flow controls whether overflow items are placed in new rows (row, default) or new columns (column).
Live demo — 3-column grid with 8 items; rows 2 and 3 are implicit (auto-generated)
1
2
3
4 (implicit row)
5
6
7 (implicit)
8
🎨 CSS — Controlling the implicit grid
.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:

PropertyApplies toControls
justify-itemsContainerAligns all items along the inline (row) axis — left/right within their cell
align-itemsContainerAligns all items along the block (column) axis — top/bottom within their cell
justify-selfItemOverrides justify-items for a single item
align-selfItemOverrides align-items for a single item
justify-contentContainerDistributes columns within the container (when grid is narrower than container)
align-contentContainerDistributes rows within the container (when grid is shorter than container)
Live demo — justify-items: center; align-items: center — items centred within their cells
A (centred)
B
C
🎨 CSS — Grid alignment
.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.

🎨 CSS — Holy Grail layout
.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

Live demo — first image spans 2 columns and 2 rows
🎨 CSS — Gallery with featured item
.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

🎨 CSS — Self-adapting card grid
.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

PropertyValues / exampleWhat it does
displaygrid / inline-gridActivates Grid on the container
grid-template-columns1fr 1fr, 200px 1fr, repeat(3, 1fr)Defines the explicit column tracks
grid-template-rows60px 1fr autoDefines the explicit row tracks
grid-template-areas"header header" "sidebar main"Assigns named areas to the grid cells
gaplength / two lengthsSpace between tracks (row-gap column-gap)
grid-auto-rows100px, minmax(80px, auto)Sets height of implicitly-created rows
grid-auto-flowrow / column / row denseControls auto-placement direction; dense backfills gaps
justify-itemsstretch / start / center / endAligns all items along the inline axis within their cells
align-itemsstretch / start / center / endAligns all items along the block axis within their cells

Items

PropertyValues / exampleWhat it does
grid-column1 / 3, span 2, 1 / -1Sets column start and end lines
grid-row2 / 4, span 2Sets row start and end lines
grid-areaheader (named) or 1/1/3/4 (lines)Assigns item to named area, or sets all four lines at once
justify-selfstart / center / end / stretchOverrides justify-items for this item
align-selfstart / center / end / stretchOverrides align-items for this item

Functions

FunctionExampleWhat it does
repeat()repeat(4, 1fr)Repeats a track size N times
minmax()minmax(200px, 1fr)Sets a minimum and maximum track size
auto-fitrepeat(auto-fit, minmax(…))As many columns as fit; empty tracks collapse
auto-fillrepeat(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.

Exercise 1
Build the classic three-column editorial layout: a wide main article in the centre, a narrow table-of-contents column on the left, and an even narrower "related links" column on the right. The columns should be in a ratio of roughly 1:3:1. Use 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.
Hint: 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.
HTML
<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>
CSS
.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; }
Exercise 2
Build a responsive card grid for a product listing. Create at least 8 cards. The grid should automatically flow between 1 and 4 columns based on the container width, with no media queries. Each card should have a minimum width of 220px. The grid should never produce columns narrower than 220px, but should stretch columns to fill any leftover space. Cards should have equal height within each row.
Hint: 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.
CSS
.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; }
Exercise 3
Create a magazine-style featured article grid. You need 6 articles in total. The first article (the "lead story") should span 2 columns and 2 rows. The second and third articles should each span 1 column and 1 row. Articles 4, 5, and 6 should sit below, spanning one column each. Use a 3-column grid with fixed row heights. Each article should have a coloured image area, a headline, and a byline.
Hint: apply 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.
HTML
<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>
CSS
.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; }
Exercise 4
Build a complete dashboard layout. It should have: a fixed top navigation bar (using 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.
Hint: the outer layout is 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.
HTML structure
<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>
CSS
/* 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; }