CSS Grid is a two-dimensional layout system โ the first native CSS tool that lets you control rows and columns simultaneously. While Flexbox excels at one-dimensional layouts (a row of buttons, a stacked column), Grid is designed for two-dimensional structures: page layouts, dashboards, photo galleries, and any UI that has both rows and columns. In this lesson you will learn how to activate a grid container, define column and row tracks, and understand the fr unit that makes Grid uniquely powerful.
Grid Fundamentals
| Term | Definition | CSS Property |
|---|---|---|
| Grid container | The parent element with display: grid |
display: grid or display: inline-grid |
| Grid items | Direct children of the grid container | Automatic โ no extra property needed |
| Grid lines | The dividing lines between rows and columns โ numbered from 1 | Referenced by grid-column, grid-row |
| Grid tracks | The columns and rows themselves โ space between two lines | grid-template-columns, grid-template-rows |
| Grid cell | The intersection of a row track and a column track | Items placed in cells by default |
| Grid area | One or more cells combined โ a rectangular region | grid-area, grid-template-areas |
Defining Columns and Rows
| Syntax | Result | Example |
|---|---|---|
200px 200px 200px |
Three fixed 200px columns | grid-template-columns: 200px 200px 200px |
repeat(3, 1fr) |
Three equal fluid columns | grid-template-columns: repeat(3, 1fr) |
1fr 2fr 1fr |
Side columns half the width of centre | grid-template-columns: 1fr 2fr 1fr |
repeat(3, 200px) |
Shorthand for three 200px columns | grid-template-columns: repeat(3, 200px) |
auto-fill with minmax |
Responsive โ as many columns as fit | repeat(auto-fill, minmax(200px, 1fr)) |
The fr Unit
| Value | Meaning | Compared to Flexbox |
|---|---|---|
1fr |
1 fraction of the available free space after fixed tracks are placed | Like flex: 1 but for grid tracks |
2fr |
Twice as much free space as 1fr columns |
Like flex: 2 |
minmax(200px, 1fr) |
At least 200px, grows to fill free space | No direct Flexbox equivalent |
min-content |
Track is as narrow as the widest single word | Like flex-basis: min-content |
max-content |
Track is as wide as the widest single-line content | Like flex-basis: max-content |
fr unit distributes free space โ what remains after fixed, auto, and min-content tracks are sized. This means 1fr 200px 1fr first reserves 200px for the middle column, then splits whatever space is left equally between the two fr columns โ far more predictable than percentages.repeat(auto-fill, minmax(250px, 1fr)) is one of the most powerful single lines in CSS. It creates as many columns as will fit at 250px minimum width, stretches them to fill the row, and automatically wraps to a new row โ a fully responsive grid with no media queries needed.gap is a relatively new addition, CSS Grid had gap (originally grid-gap) from the beginning. Always use gap (not margins) for spacing between grid items โ it applies only between tracks, never on the outer edges, and works in both row and column directions.Basic Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Grid Container and Tracks</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; padding: 32px; background: #f8fafc; }
h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em;
color: #64748b; margin: 24px 0 8px; }
/* โโ Three equal columns with fr โโ */
.grid-equal {
display: grid;
grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
gap: 12px;
margin-bottom: 24px;
}
/* โโ Mixed fixed + fluid columns โโ */
.grid-mixed {
display: grid;
grid-template-columns: 180px 1fr 2fr; /* fixed | fluid | double fluid */
gap: 12px;
margin-bottom: 24px;
}
/* โโ Explicit rows + columns โโ */
.grid-explicit {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 60px 120px; /* row 1: 60px, row 2: 120px */
gap: 12px;
margin-bottom: 24px;
}
/* โโ Auto-responsive grid โ no media queries โโ */
.grid-auto {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}
/* Shared item style */
.item {
background: #4f46e5;
color: white;
border-radius: 8px;
padding: 16px;
font-size: 0.8rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.item:nth-child(2n) { background: #7c3aed; }
.item:nth-child(3n) { background: #0891b2; }
</style>
</head>
<body>
<h3>repeat(3, 1fr) โ three equal columns</h3>
<div class="grid-equal">
<div class="item">1fr</div>
<div class="item">1fr</div>
<div class="item">1fr</div>
</div>
<h3>180px 1fr 2fr โ fixed | fluid | double</h3>
<div class="grid-mixed">
<div class="item">180px (fixed)</div>
<div class="item">1fr</div>
<div class="item">2fr (double)</div>
</div>
<h3>Explicit rows: 60px then 120px</h3>
<div class="grid-explicit">
<div class="item">60px</div>
<div class="item">60px</div>
<div class="item">60px</div>
<div class="item">60px</div>
<div class="item">120px</div>
<div class="item">120px</div>
<div class="item">120px</div>
<div class="item">120px</div>
</div>
<h3>auto-fill minmax(160px, 1fr) โ resize window!</h3>
<div class="grid-auto">
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
<div class="item">D</div>
<div class="item">E</div>
<div class="item">F</div>
</div>
</body>
</html>
How It Works
Step 1 โ display: grid Activates the Grid Context
Adding display: grid to a parent makes all direct children grid items. Unlike Flexbox, a grid does nothing visually until you define columns โ items stack in a single column by default (the browser creates one implicit column track per item).
Step 2 โ repeat(3, 1fr) Divides Space Equally
The browser calculates total container width, subtracts gap space (2 gaps of 12px = 24px), then divides the remainder equally among 3 fr columns. At 900px container: (900 – 24) / 3 = 292px per column. Resize the window and all three columns resize together.
Step 3 โ Mixed Tracks Size in Order
In 180px 1fr 2fr, the browser first reserves 180px for column 1 and the two gaps (24px total), then distributes the remaining space: 1fr gets one share, 2fr gets two shares. At 900px: remaining = 900 – 180 – 24 = 696px; 1fr = 232px; 2fr = 464px.
Step 4 โ grid-template-rows Sets Explicit Row Heights
grid-template-rows: 60px 120px gives the first row a fixed 60px height and the second row 120px. Items in row 1 are sized to the track โ they stretch to fill 60px. Additional rows beyond the defined tracks use the browser’s implicit row sizing (auto).
Step 5 โ auto-fill + minmax Creates Responsive Columns Automatically
repeat(auto-fill, minmax(160px, 1fr)) tells the browser to fill the row with as many 160px+ columns as will fit. On a 700px container: 4 columns fit (4 ร 160 + 3 ร 12 = 676px); on 400px: 2 columns fit. Each column stretches to 1fr to fill the row. Zero media queries required.
Real-World Example: Dashboard Metrics Grid
/* dashboard-grid.css */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #f1f5f9; padding: 32px; }
/* โโ Outer page layout: sidebar + main โโ */
.dashboard {
display: grid;
grid-template-columns: 240px 1fr; /* fixed sidebar, fluid main */
grid-template-rows: auto 1fr auto; /* header, content, footer */
min-height: 100vh;
gap: 0;
}
/* โโ Stat cards: auto-responsive โโ */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: white;
border-radius: 12px;
padding: 20px 24px;
border: 1px solid #e2e8f0;
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.stat-label { font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: #64748b; margin-bottom: 8px; }
.stat-value { font-size: 2rem; font-weight: 800; color: #0f172a; line-height: 1; }
.stat-delta { font-size: 0.8rem; font-weight: 600; margin-top: 6px; }
.delta-up { color: #10b981; }
.delta-down { color: #ef4444; }
/* โโ Chart area: 2 wide + 1 narrow โโ */
.charts-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
.chart-card {
background: white;
border-radius: 12px;
padding: 24px;
border: 1px solid #e2e8f0;
min-height: 260px;
}
Common Mistakes
Mistake 1 โ Using percentages instead of fr for equal columns
โ Wrong โ percentages do not account for gap, causing overflow:
.grid { display: grid; grid-template-columns: 33.33% 33.33% 33.33%; gap: 16px; }
/* Total = 100% + 2 x 16px gap = overflow! */
โ Correct โ fr automatically deducts gap before distributing space:
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
Mistake 2 โ Confusing auto-fill with auto-fit
โ Wrong โ auto-fit collapses empty tracks, stretching remaining items unexpectedly:
.grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
/* With 2 items in a 900px container: each item becomes 450px */
โ Correct โ auto-fill keeps empty track slots, items stay at minmax size:
.grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
/* With 2 items in a 900px container: items are 200px, rest is empty */
Mistake 3 โ Setting gap on items instead of the container
โ Wrong โ margin on items creates uneven outer spacing:
.grid { display: grid; grid-template-columns: repeat(3, 1fr); }
.item { margin: 8px; } /* adds gaps AND outer spacing โ uneven edges */
โ Correct โ gap on the container, no margin on items:
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
Quick Reference
| Property | Example | Result |
|---|---|---|
display: grid |
display: grid |
Activates grid on container |
grid-template-columns |
repeat(3, 1fr) |
3 equal columns |
grid-template-rows |
80px auto |
Fixed header, auto body |
gap |
gap: 16px |
Between-track spacing only |
fr |
1fr 2fr |
Fractional free space โ after fixed tracks |
minmax(min, max) |
minmax(200px, 1fr) |
At least 200px, grows to 1fr |
repeat(auto-fill, ...) |
repeat(auto-fill, minmax(200px, 1fr)) |
Responsive โ as many columns as fit |