Chapter 16: CSS Grid Layout — Complete Guide
CSS Grid Layout is the most powerful 2D layout engine in web design. Unlike 1D Flexbox, Grid manages both rows and columns simultaneously, enabling precise page architecture, named template areas, subgrid inheritance, and responsive 12-column grid systems.
16.1 Understanding 2D CSS Grid & The Grid Blueprint
CSS Grid creates an explicit or implicit 2D coordinate grid defined by grid lines, grid tracks (columns & rows), and grid cells. Every grid item lives in one or more cells:
| Grid Concept | Definition | Example |
|---|---|---|
| Grid Container | The parent element with display: grid or display: inline-grid |
.wrapper { display: grid; } |
| Grid Lines | Numbered lines (1-based) that divide the grid into tracks. Negative indices count from the end. | Column line 1, row line -1 (last) |
| Grid Track | A single row or column — the space between two adjacent grid lines. | The area between column lines 1 and 2 |
| Grid Cell | The intersection of one row track and one column track — the smallest unit. | Cell at column 2, row 3 |
| Grid Area | Any rectangular area composed of one or more grid cells. | An item spanning columns 1–3, row 1 |
16.2 Grid Container Properties — Complete Reference
Container-level properties define the grid structure. These are applied to the parent element:
| Property | Key Values / Syntax | Description |
|---|---|---|
display |
grid, inline-grid |
Activates grid context. inline-grid keeps the container inline. |
grid-template-columns |
1fr 2fr 1fr, repeat(3, 1fr), 200px auto 1fr |
Defines column track sizes. Accepts px, %, fr, auto, minmax(), repeat(). |
grid-template-rows |
auto, 100px 1fr, repeat(2, 60px) |
Defines row track sizes. auto = sized by content. |
grid-template-areas |
"header header" "sidebar main" "footer footer" |
ASCII-art visual layout map using named areas. Each string = one row. |
grid-template |
Shorthand combining rows, columns, and areas | Combines grid-template-rows, -columns, and -areas. |
gap (row-gap / column-gap) |
16px, 1rem 2rem |
Sets gutter spacing between tracks. No margin hacks needed. |
justify-items |
start, end, center, stretch |
Aligns all items horizontally within their grid cells. Default: stretch. |
align-items |
start, end, center, stretch |
Aligns all items vertically within their grid cells. |
justify-content |
start, end, center, space-between, space-around |
Distributes the entire grid horizontally within the container. |
align-content |
start, end, center, space-between |
Distributes the entire grid vertically within the container. |
grid-auto-flow |
row, column, row dense, column dense |
Controls how implicitly-placed items fill the grid. dense fills holes. |
grid-auto-rows / grid-auto-columns |
auto, 100px, minmax(80px, auto) |
Sets sizes for implicitly-created tracks. |
16.3 Key Grid Functions: fr, repeat(), minmax()
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));repeat(auto-fit, ...) = create as many columns as fit in the container→
minmax(200px, 1fr) = each column is at least 200px, grows to 1fr→ Result: Zero media queries needed for a fully responsive card grid!
| Function / Unit | Syntax | Description |
|---|---|---|
fr (Fractional Unit) |
1fr 2fr 1fr |
Shares remaining free space proportionally. 2fr = twice as wide as 1fr. |
repeat(N, size) |
repeat(4, 1fr) |
Repeats a track definition N times. Avoids repetitive code. |
minmax(min, max) |
minmax(150px, 1fr) |
Track is at least min, at most max. Enables fluid, bounded tracks. |
auto-fill |
repeat(auto-fill, minmax(200px, 1fr)) |
Fills as many columns as possible. Keeps empty columns as space at the end. |
auto-fit |
repeat(auto-fit, minmax(200px, 1fr)) |
Fills as many columns as possible. Collapses empty columns to 0 — items stretch to fill. Preferred for card grids! |
fit-content(max) |
fit-content(300px) |
Track is sized by content, capped at the specified maximum. |
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; display: flex; flex-direction: column; gap: 24px; }
h4 { color: #38bdf8; margin: 0 0 8px; font-size: 13px; }
.label { font-size: 11px; color: #64748b; font-family: monospace; margin-bottom: 6px; }
.card { background: #1e293b; border: 1px solid #334155; border-radius: 10px; padding: 14px; font-size: 12px; color: #94a3b8; }
.card strong { color: #f8fafc; display: block; margin-bottom: 4px; }
/* auto-fit: collapses empty columns — items fill full width */
.grid-autofit {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
/* fr unit: proportional column widths */
.grid-fr {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* sidebar | main | sidebar */
gap: 12px;
}
</style>
</head>
<body>
<div>
<h4>auto-fit + minmax() — Responsive card grid with zero media queries</h4>
<p class="label">grid-template-columns: repeat(auto-fit, minmax(180px, 1fr))</p>
<div class="grid-autofit">
<div class="card"><strong>Card 1</strong>Grows to fill space</div>
<div class="card"><strong>Card 2</strong>Minimum 180px wide</div>
<div class="card"><strong>Card 3</strong>Maximum 1fr wide</div>
<div class="card"><strong>Card 4</strong>Auto-wraps below 180px</div>
</div>
</div>
<div>
<h4>fr units — 1fr sidebar | 2fr main | 1fr sidebar</h4>
<p class="label">grid-template-columns: 1fr 2fr 1fr</p>
<div class="grid-fr">
<div class="card" style="border-color:#f59e0b;"><strong style="color:#fbbf24;">Left Sidebar</strong>1fr</div>
<div class="card" style="border-color:#10b981;"><strong style="color:#34d399;">Main Content</strong>2fr — double width</div>
<div class="card" style="border-color:#f59e0b;"><strong style="color:#fbbf24;">Right Sidebar</strong>1fr</div>
</div>
</div>
</body>
</html>
16.4 auto-fill vs auto-fit — The Critical Difference
This is one of the most commonly confused concepts in CSS Grid. Both auto-fill and auto-fit create as many tracks as possible — but they handle empty tracks differently:
| Keyword | Empty Track Behavior | Result With 2 Items in a Wide Container | Best Used For |
|---|---|---|---|
auto-fill |
Keeps empty tracks (reserves space) | Items stay at their minimum width; empty column space remains on the right | Grids where you need consistent column positions (e.g., known future items) |
auto-fit |
Collapses empty tracks to 0 | Items stretch to fill all available space (grow to 1fr each) | Card grids — items always fill the full container width |
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; display: flex; flex-direction: column; gap: 20px; }
h4 { color: #6366f1; margin: 0 0 6px; }
.label { font-size: 11px; font-family: monospace; color: #64748b; margin-bottom: 8px; }
.card { background: #1e293b; border: 2px solid #334155; border-radius: 10px; padding: 16px; font-weight: bold; font-size: 13px; text-align: center; color: #a5b4fc; }
/* auto-fill: reserves space for potential extra columns */
.grid-fill { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
/* auto-fit: collapses empty columns — items expand to fill container */
.grid-fit { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
</style>
</head>
<body>
<div>
<h4>auto-fill — Items stay at min-width, empty space preserved</h4>
<p class="label">repeat(auto-fill, minmax(140px, 1fr)) — only 2 items</p>
<div class="grid-fill">
<div class="card" style="border-color:#38bdf8;">Item A</div>
<div class="card" style="border-color:#38bdf8;">Item B</div>
</div>
</div>
<div>
<h4>auto-fit — Empty columns collapse; items fill full width</h4>
<p class="label">repeat(auto-fit, minmax(140px, 1fr)) — same 2 items</p>
<div class="grid-fit">
<div class="card" style="border-color:#10b981;color:#34d399;">Item A — fills container</div>
<div class="card" style="border-color:#10b981;color:#34d399;">Item B — fills container</div>
</div>
</div>
</body>
</html>
16.5 Grid Named Template Areas (grid-template-areas)
grid-template-areas provides a visual ASCII-art mapping syntax to define page layout regions. Each quoted string represents one row, with area names mapping to element positions:
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h3 { margin-top: 0; color: #818cf8; }
/* The ASCII map defines the layout visually */
.app-layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar main main"
"sidebar widgets widgets"
"footer footer footer";
grid-template-columns: 180px 1fr 1fr;
grid-template-rows: 60px 1fr auto 50px;
gap: 10px;
min-height: 300px;
}
.area { border-radius: 8px; padding: 14px; font-size: 13px; font-weight: bold; display: flex; align-items: center; justify-content: center; }
.area-header { grid-area: header; background: rgba(99,102,241,0.3); border: 2px solid #6366f1; color: #a5b4fc; }
.area-sidebar { grid-area: sidebar; background: rgba(245,158,11,0.2); border: 2px solid #f59e0b; color: #fbbf24; }
.area-main { grid-area: main; background: rgba(16,185,129,0.2); border: 2px solid #10b981; color: #34d399; }
.area-widgets { grid-area: widgets; background: rgba(56,189,248,0.15); border: 2px solid #38bdf8; color: #7dd3fc; }
.area-footer { grid-area: footer; background: rgba(30,41,59,0.8); border: 1px solid #334155; color: #64748b; font-size: 12px; }
</style>
</head>
<body>
<h3>grid-template-areas — Full Page Layout</h3>
<div class="app-layout">
<header class="area area-header">Header (grid-area: header — spans 3 cols)</header>
<aside class="area area-sidebar">Sidebar<br/>grid-area: sidebar</aside>
<main class="area area-main">Main Content (grid-area: main)</main>
<section class="area area-widgets">Widgets Panel (grid-area: widgets)</section>
<footer class="area area-footer">Footer — grid-area: footer — spans all 3 columns</footer>
</div>
</body>
</html>
16.6 Grid Item Placement (grid-column, grid-row, span)
Place items explicitly using grid line numbers or the span keyword:
| Property | Syntax | Description |
|---|---|---|
grid-column |
1 / 3, 1 / -1, span 2 |
Shorthand for grid-column-start / grid-column-end. Use negative index for last line. |
grid-row |
1 / 2, 2 / span 3 |
Shorthand for grid-row-start / grid-row-end. |
grid-area |
header, 1 / 1 / 2 / 4 |
Maps item to a named area OR shorthand for row-start / col-start / row-end / col-end. |
justify-self |
start, end, center, stretch |
Overrides justify-items for this individual item (horizontal alignment in cell). |
align-self |
start, end, center, stretch |
Overrides align-items for this individual item (vertical alignment in cell). |
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
h4 { color: #f59e0b; margin: 0 0 10px; }
.grid { display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(3, 60px); gap: 8px; }
.item { background: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 8px; font-size: 11px; text-align: center; color: #94a3b8; font-family: monospace; display: flex; align-items: center; justify-content: center; }
/* Full-width header */
.full-row { grid-column: 1 / -1; background: rgba(99,102,241,0.3); border-color: #6366f1; color: #a5b4fc; font-weight: bold; }
/* Double-wide item */
.span-2 { grid-column: span 2; background: rgba(16,185,129,0.25); border-color: #10b981; color: #34d399; font-weight: bold; }
/* Item placed at specific row/column */
.placed { grid-column: 4; grid-row: 2 / 4; background: rgba(245,158,11,0.25); border-color: #f59e0b; color: #fbbf24; font-weight: bold; }
</style>
</head>
<body>
<h4>Grid Item Placement Demo (4-column × 3-row grid)</h4>
<div class="grid">
<div class="item full-row">grid-column: 1 / -1 (full width header)</div>
<div class="item span-2">grid-column: span 2</div>
<div class="item">Item 3</div>
<div class="item placed">grid-column: 4<br/>grid-row: 2 / 4</div>
<div class="item">Item 5</div>
<div class="item">Item 6</div>
<div class="item">Item 7</div>
</div>
</body>
</html>
16.7 grid-auto-flow: dense — Filling Grid Holes
When items have varying spans, the default auto placement algorithm can leave holes. grid-auto-flow: dense automatically backfills those gaps with smaller items — perfect for masonry-style layouts:
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; display: flex; gap: 24px; flex-wrap: wrap; }
h4 { color: #38bdf8; margin: 0 0 8px; font-size: 13px; }
.wrapper { flex: 1 1 300px; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.dense { grid-auto-flow: dense; } /* Backfills holes! */
.item { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 12px; font-size: 11px; font-weight: bold; text-align: center; height: 60px; display: flex; align-items: center; justify-content: center; }
.wide { grid-column: span 2; background: rgba(16,185,129,0.25); border-color: #10b981; color: #34d399; }
.tall { grid-row: span 2; height: auto; background: rgba(245,158,11,0.25); border-color: #f59e0b; color: #fbbf24; }
.i1 { color: #a5b4fc; } .i2 { color: #7dd3fc; } .i3 { color: #f9a8d4; }
</style>
</head>
<body>
<div class="wrapper">
<h4>Without dense — holes appear</h4>
<div class="grid">
<div class="item wide">Wide (span 2)</div>
<div class="item i1">Item 2</div>
<div class="item i2">Item 3</div>
<div class="item wide">Wide (span 2)</div>
<div class="item i3">Item 5</div>
</div>
</div>
<div class="wrapper">
<h4>With dense — holes backfilled automatically!</h4>
<div class="grid dense">
<div class="item wide">Wide (span 2)</div>
<div class="item i1">Item 2</div>
<div class="item i2">Item 3</div>
<div class="item wide">Wide (span 2)</div>
<div class="item i3">Item 5 — fills gap!</div>
</div>
</div>
</body>
</html>
16.8 Responsive 12-Column Grid System
Building a lightweight, custom 12-column grid layout system from scratch using CSS Grid — the same pattern used by Bootstrap and Tailwind CSS under the hood:
<!DOCTYPE html>
<html>
<head>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; display: flex; flex-direction: column; gap: 16px; }
h3 { margin-top: 0; color: #10b981; }
/* 12-Column Grid Container */
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 10px;
}
/* Column span utilities */
.col-12 { grid-column: span 12; }
.col-8 { grid-column: span 8; }
.col-6 { grid-column: span 6; }
.col-4 { grid-column: span 4; }
.col-3 { grid-column: span 3; }
.col-2 { grid-column: span 2; }
.box {
background: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
padding: 12px 10px;
font-size: 11px;
font-weight: bold;
text-align: center;
color: #64748b;
font-family: monospace;
}
</style>
</head>
<body>
<h3>12-Column CSS Grid System</h3>
<div class="grid-12">
<div class="box col-12" style="border-color:#6366f1;color:#a5b4fc;">col-12 (Full Width)</div>
<div class="box col-8" style="border-color:#10b981;color:#34d399;">col-8 (Main Content)</div>
<div class="box col-4" style="border-color:#f59e0b;color:#fbbf24;">col-4 (Sidebar)</div>
<div class="box col-6" style="border-color:#38bdf8;color:#7dd3fc;">col-6</div>
<div class="box col-6" style="border-color:#38bdf8;color:#7dd3fc;">col-6</div>
<div class="box col-4">col-4</div>
<div class="box col-4">col-4</div>
<div class="box col-4">col-4</div>
<div class="box col-3" style="border-color:#ec4899;color:#f472b6;">col-3</div>
<div class="box col-3" style="border-color:#ec4899;color:#f472b6;">col-3</div>
<div class="box col-3" style="border-color:#ec4899;color:#f472b6;">col-3</div>
<div class="box col-3" style="border-color:#ec4899;color:#f472b6;">col-3</div>
</div>
</body>
</html>
16.9 Interactive Sandbox — CSS Grid Playground
Experiment with grid-template-areas, auto-fit vs auto-fill, and the 12-column system live: