CSS Grid Fundamentals
Two-Dimensional Layout, Solved
CSS Grid is the first CSS layout system actually designed for two dimensions. While flexbox handles a single row or column, Grid handles rows AND columns simultaneously. It controls both where items go and how much space they take -- across both axes at once.
The thing that makes Grid special: the container defines the layout structure. Items just slot into that structure. No wrapper divs. No layout hacks.
Think of CSS Grid as a spreadsheet. The container defines the column widths and row heights (the grid template). Items are placed into cells by coordinates (grid lines). An item can span multiple cells like a merged cell. The fr unit distributes remaining space like proportional column widths in a table. The grid lines are numbered starting at 1, not 0.
Defining a Grid
.grid {
display: grid;
/* Define columns */
grid-template-columns: 200px 1fr 200px; /* 3 columns: fixed, flexible, fixed */
/* Define rows */
grid-template-rows: auto 1fr auto; /* 3 rows: content-sized, flexible, content-sized */
/* Gap between cells */
gap: 1rem;
}
The fr Unit
fr means "fraction of remaining space" — after fixed sizes and gaps are subtracted:
.grid {
grid-template-columns: 200px 1fr 2fr;
/* Container: 900px, gap: 0 */
/* Fixed: 200px. Remaining: 700px */
/* 1fr = 700/3 = 233.3px */
/* 2fr = 700 × 2/3 = 466.7px */
}
And here's a detail that saves you from a lot of headaches: fr differs from percentages because 1fr accounts for gaps automatically, while 33% doesn't. With gap: 20px, three 1fr columns always fit perfectly, but three 33.33% columns overflow by the gap amount.
Repeat Function
.grid {
/* 12 equal columns */
grid-template-columns: repeat(12, 1fr);
/* 3 columns of 200px */
grid-template-columns: repeat(3, 200px);
/* Pattern: 100px then 1fr, repeated 3 times */
grid-template-columns: repeat(3, 100px 1fr);
}
auto-fill vs auto-fit
These two get confused constantly. Both create responsive grids without media queries, but they differ when there's extra space:
/* auto-fill: creates as many columns as fit, even empty ones */
.grid-fill {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
/* With a 900px container: 3 columns of 300px */
/* With a 1400px container: 5 columns of 280px */
/* Empty tracks remain if there aren't enough items */
}
/* auto-fit: like auto-fill, but collapses empty tracks */
.grid-fit {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
/* 3 items in a 900px container: 3 columns of 300px (same) */
/* 3 items in a 1400px container: 3 columns of ~466px (items stretch) */
/* Empty tracks collapse — existing items fill the space */
}
When to use each:
auto-fill: When you want consistent column sizes even with few items (the grid structure stays even with empty cells)auto-fit: When you want items to stretch to fill available space (more common)
Why minmax(250px, 1fr) is the magic formula
minmax(250px, 1fr) means "each column is at least 250px but can grow to fill available space." Combined with auto-fit, the browser: (1) calculates how many 250px columns fit, (2) creates that many tracks, (3) distributes leftover space equally using the 1fr maximum. As the viewport shrinks, columns naturally reduce — when fewer than two 250px columns fit, you get a single column. No media queries needed.
Placing Items
Line-Based Placement
Grid lines are numbered starting at 1. Negative numbers count from the end:
.header {
grid-column: 1 / -1; /* Span all columns (line 1 to last line) */
grid-row: 1; /* First row */
}
.sidebar {
grid-column: 1 / 2; /* Column 1 only */
grid-row: 2 / 4; /* Rows 2 through 3 (span 2 rows) */
}
.content {
grid-column: 2 / -1; /* Column 2 to end */
grid-row: 2 / 3; /* Row 2 only */
}
Span Keyword
.featured {
grid-column: span 2; /* Span 2 columns from auto-placed position */
grid-row: span 2; /* Span 2 rows */
}
Auto Placement
Without explicit placement, items fill cells left-to-right, top-to-bottom:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(100px, auto); /* Implicit rows size */
grid-auto-flow: row; /* Default: fill rows first */
grid-auto-flow: dense; /* Fill holes left by larger items */
}
grid-auto-flow: dense fills visual gaps in the grid by reordering items. This means DOM order and visual order diverge — breaking keyboard navigation and screen reader expectations. Only use dense for content where order doesn't matter (like image galleries), never for navigation or sequential content.
Implicit vs Explicit Grid
This distinction matters more than you'd think.
.grid {
grid-template-columns: repeat(3, 1fr); /* Explicit: 3 columns */
grid-template-rows: 100px 200px; /* Explicit: 2 rows */
/* What happens when items exceed 2 rows? */
grid-auto-rows: minmax(100px, auto); /* Implicit rows: at least 100px */
}
Explicit grid: what you define with grid-template-*. Implicit grid: what the browser creates when items don't fit in the explicit grid. Use grid-auto-rows and grid-auto-columns to control implicit track sizing.
Production Scenario: Dashboard Grid
.dashboard {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: auto;
gap: 1.5rem;
}
/* Full-width header */
.stat-bar { grid-column: 1 / -1; }
/* Main chart takes 8 of 12 columns */
.chart { grid-column: span 8; }
/* Sidebar takes remaining 4 */
.sidebar-widget { grid-column: span 4; }
/* Responsive: stack on mobile */
@media (max-width: 768px) {
.dashboard { grid-template-columns: 1fr; }
.chart, .sidebar-widget { grid-column: 1 / -1; }
}
| What developers do | What they should do |
|---|---|
| Using percentage widths instead of fr in grid columns Three columns of 33.33% + gap overflows. Three columns of 1fr + gap fits perfectly. | Use fr — it accounts for gaps automatically, percentages don't |
| Confusing auto-fill and auto-fit (using auto-fill when items should stretch) auto-fill keeps empty tracks. auto-fit collapses them, letting items stretch. | Use auto-fit when you want items to expand to fill space, auto-fill for consistent column sizes |
| Not setting grid-auto-rows when item count is dynamic Without it, implicit rows default to auto sizing which might not be what you want | Always define grid-auto-rows: minmax(min, auto) for dynamic content |
| Remembering grid lines start at 0 Unlike array indices, CSS grid lines are 1-based. -1 refers to the last line. | Grid lines start at 1. The first column is between lines 1 and 2. |
- 1fr distributes remaining space after fixed sizes and gaps — always prefer fr over percentages in grid
- 2auto-fit collapses empty tracks (items stretch). auto-fill preserves empty tracks (consistent sizing).
- 3minmax(250px, 1fr) with auto-fit creates responsive grids without media queries
- 4Grid lines are 1-based. Use -1 to reference the last line.
- 5Use grid-auto-rows for dynamic content that may exceed the explicit grid