Lesson 5

CSS Grid

Home › Courses › CSS › Lesson 5
🔵 Lesson 5 · 25 minutes

CSS Grid.
Two dimensions. Total control.

Flexbox arranges items in a row or a column. Grid arranges them in rows AND columns simultaneously — the two-dimensional layout system that finally made complex page layouts simple. Dashboards, article pages, image galleries, app shells — all became one-line layout declarations the day grid shipped. This is the last piece of the modern CSS layout toolkit, and once you have it, no layout problem is out of reach.

~25 minutes Intermediate CSS3 · grid
CSS path ← Lesson 4 Basics The fr unit repeat & minmax auto-fit Placement grid-areas Patterns Challenge

Add display: grid to a parent to make it a grid container. Define columns with grid-template-columns and rows with grid-template-rows. Use the fr unit for flexible columns, repeat(n, ...) for shortcuts, and minmax(min, max) for responsive tracks. Children are placed in cells automatically, or positioned explicitly with grid-column / grid-row. For complex layouts, use grid-template-areas — the layout is written as ASCII art you can read at a glance.

Before this lesson: complete Lesson 4. Grid and flexbox complement each other — knowing both is essential.

🎯 The basics — display: grid

One line turns a parent into a grid container. Children snap into rows and columns.

CSS
/* The container — becomes a grid container /
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr; / 3 equal columns /
grid-template-rows: 80px 80px; / 2 fixed-height rows */
gap: 12px;
padding: 16px;
background: rgba(21, 114, 182, 0.1);
border-radius: 8px;
}

/* The children — automatically placed into cells, left to right, top to bottom */
.grid > div {
background: #1572B6;
color: white;
padding: 16px;
border-radius: 6px;
text-align: center;
font-family: system-ui, sans-serif;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}

display: grid

Turns the element into a grid container. Direct children become grid items. You define the track structure with grid-template-columns and grid-template-rows.

Auto-placement

Children are placed automatically, left to right, top to bottom, filling cells in order. You don't have to position each one — the browser does it. Explicit placement is available but rarely needed for simple grids.

Two-dimensional

Grid controls both rows AND columns. Flexbox controls one dimension. This is the entire conceptual difference — and the reason grid is essential for page-level layouts where you need precise control over both axes.

gap works here too

Same gap property as flexbox — spacing between grid cells. Row and column gaps can differ: gap: 16px 8px means 16px between rows, 8px between columns.

Grid vs flexbox — the one-question rule. Ask: does this layout need to control rows AND columns at the same time? If yes → grid. If it's just "items in a line, one direction" → flexbox. Nav bars, button groups, card contents, tag clouds — flexbox. Dashboards, article pages, image galleries, app shells — grid. Modern sites often use both on the same page: grid for the overall structure, flexbox for components inside each grid cell.

⚖️ The fr unit — fractions, not percentages

The single most-used grid unit. Makes flexible columns effortless.

CSS
/* Three equal columns */
.equal {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}

/* Two-thirds + one-third — 2:1 ratio */
.two-thirds {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 12px;
}

/* Fixed sidebar + flexible main */
.sidebar-layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 16px;
}

/* Two fixed sidebars + flexible main */
.full-layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
gap: 16px;
}

/* Mixed units — px, fr, and auto can coexist */
.mixed {
display: grid;
grid-template-columns: 100px 1fr auto;
gap: 12px;
}

1fr

One fraction of the free space. 1fr 1fr 1fr creates three equal columns. If one column is 2fr, it gets twice the space of a 1fr column.

fr vs %

Percentages include the gaps in their calculation — so 33% 33% 33% with a gap overflows. fr units split the space after gaps, so they always fit exactly. This is why fr is preferred over percentages for grids.

Mixing units

You can combine px, fr, auto, min-content, and max-content in one grid. Fixed values consume their space first, then fr divides what's left.

auto

Content-based sizing. auto means "as wide as the content wants to be." Useful for columns that should size to their content, like a buttons column or a badge column.

The fr unit's killer feature: no math. With percentages, adding a gap meant recalculating — 33% 33% 33% plus 16px gaps overflows by 32px. You'd have to write calc((100% - 32px) / 3) for each column. With fr, you just write 1fr 1fr 1fr and the browser handles the gap math automatically. This single feature is why grid replaced older layout techniques.

🔁 repeat() and minmax()

Two functions that turn grid into a responsive layout engine.

CSS
/* repeat(n, size) — creates n tracks of the given size /
.repeat-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
/ Same as: grid-template-columns: 1fr 1fr 1fr */

/* repeat with a mix — repeat 3 columns of 200px, then a 1fr */
.mixed-repeat {
display: grid;
grid-template-columns: repeat(3, 200px) 1fr;
gap: 12px;
}

/* minmax(min, max) — a track that's at least min wide, at most max/
.minmax-demo {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
gap: 12px;
}
/ Each column is at least 200px, but grows to fill space */

/* THE responsive pattern — auto-fit + minmax /
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
/ Creates as many 250px+ columns as fit, then stretches them.
On mobile: 1 column. Tablet: 2-3. Desktop: 4-6.
No media queries. This is the modern responsive grid. */

/* auto-fill vs auto-fit /
.auto-fill {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
/ Fills the row with tracks even if some are empty /
}
.auto-fit {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
/ Collapses empty tracks so remaining ones stretch */
}

repeat(n, size)

Shorthand for repeated tracks. repeat(3, 1fr) = 1fr 1fr 1fr. repeat(2, 200px) = 200px 200px. Saves typing and makes intent clearer.

minmax(min, max)

A track that's at least min wide, at most max. minmax(200px, 1fr) means "at least 200px, grow to fill." The core responsive primitive.

auto-fit

Creates as many tracks as fit, then collapses empty ones and stretches remaining. Use for card grids — cards fill the row when there are few, stay sized when there are many.

auto-fill

Creates as many tracks as fit, even if some are empty. Remaining tracks don't stretch. Use when you want consistent spacing regardless of card count — icon galleries, tag grids.

auto-fit vs auto-fill — when to use which. auto-fit collapses empty tracks and stretches the visible ones — so 2 cards on a wide screen span the full width. auto-fill leaves empty tracks in place — so 2 cards on a wide screen stay narrow, aligned to the left. Use auto-fit for card grids where you want cards to fill the row. Use auto-fill for consistent layouts where alignment matters more than filling. When in doubt: auto-fit.

📍 Explicit placement — spanning and positioning

Use grid-column and grid-row to span multiple cells.

CSS
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 100px);
gap: 12px;
padding: 16px;
}

/* Span 2 columns — the featured card /
.featured {
grid-column: span 2;
/ Starts at current position, spans 2 columns */
}

/* Span 2 rows — a tall card */
.tall {
grid-row: span 2;
}

/* Explicit position — start at line 1, end at line 3 /
.explicit {
grid-column: 1 / 3;
/ From line 1 to line 3 = spans 2 columns */
}

/* Explicit row position /
.header {
grid-column: 1 / -1; / -1 means "last line" — spans all columns /
grid-row: 1; / explicitly row 1 */
}

/* Place in exact grid position /
.hero {
grid-column: 1 / 3;
grid-row: 1 / 3;
/ Top-left area, spanning 2 cols x 2 rows */
}

grid-column: span N

Span N columns from the current position. span 2 = two columns wide. Use for featured cards, headlines, and any element that should occupy more than one cell.

grid-column: 1 / 3

Start at line 1, end at line 3. Grid lines are numbered starting at 1. 1 / 3 spans two columns. 2 / 4 starts at the 2nd line and goes to the 4th.

-1 = last line

grid-column: 1 / -1 means "from first line to last line" — spans all columns. Use for headers, footers, and any full-width element.

Line vs span

grid-column: 1 / 3 — explicit line numbers. grid-column: span 2 — relative to current position. Use line numbers for absolute positioning, span for auto-placed items that need to be wider.

🗺️ grid-template-areas — the visual layout

Write the layout as ASCII art. The most readable grid syntax.

CSS
/* The classic dashboard layout — written as a visual map */
.dashboard {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 80px 1fr 60px;
grid-template-areas:
"header header"
"sidebar main"
"sidebar footer";
gap: 12px;
min-height: 400px;
}

/* Place children by name */
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

/* Style for preview */
.dashboard > * {
background: #1572B6;
color: white;
padding: 16px;
border-radius: 6px;
font-family: system-ui, sans-serif;
font-weight: 600;
}

/* The header spans 2 columns because "header header" is one row */

/* You can also use dots for empty cells /
.layout-with-gap {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-areas:
"hero hero side"
". main main";
/ . means "empty cell" — no content, no background */
}

grid-template-areas

Define the layout as a grid of names. Each quoted string is a row. Each word inside is a cell. Repeated names make elements span multiple cells.

grid-area: name

Place a child into the named area. .header { grid-area: header; }. That's the entire placement — no line numbers to calculate.

Dots for empty cells

Use a period . for cells with no content. ". hero ." means "empty cell, hero, empty cell." Useful for irregular layouts.

When to use areas

Use for page-level layouts with 3+ regions (header, sidebar, main, footer, etc.). The CSS is readable at a glance — future-you can see the layout without running it. For simple equal-column grids, grid-template-columns alone is enough.

grid-template-areas is the single most readable layout syntax in CSS. Compare a dashboard built with line numbers (which requires counting columns and rows mentally) to one built with template areas (where the CSS strings literally spell out the layout). For any layout with more than 2 rows and 2 columns, template areas are dramatically easier to read, write, and modify. This is the layout tool for real-world dashboards, app shells, and article pages.

🎯 Real-world patterns

Five layouts that cover most of what you'll build. Each is a preview.

CSS — 5 patterns
/* 1. RESPONSIVE CARD GRID — the most-used grid /
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
/ No media queries. 1 column on mobile, up to 4 on desktop. */

/* 2. SIDEBAR + MAIN LAYOUT */
.sidebar-layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 24px;
}

/* 3. HOLY GRAIL — header, footer, sidebar, main */
.holy-grail {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 200px 1fr;
grid-template-rows: 60px 1fr 60px;
gap: 16px;
min-height: 400px;
}

/* 4. MASONRY-STYLE GALLERY — cards of different heights /
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
/ Use grid-auto-rows: masonry (modern) or manually place with grid-column: span 2 on some items */
}

/* 5. FEATURED CARD LAYOUT — first card spans 2 columns */
.featured-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.featured-grid > *:first-child {
grid-column: span 2;
grid-row: span 2;
}

These five patterns cover 80% of page layouts. Responsive card grid, sidebar + main, holy grail, featured card, gallery. Every dashboard, blog, product page, and app shell falls into one of these. The patterns are so common that they have names — learn the names, and you can describe any layout to any developer in one sentence.

🏆 Challenge

Three tasks. Each exercises a different grid skill.

Task 1 — Responsive card grid

Build a grid of 8 cards that:

1. Shows 1 card per row on mobile (<500px)
2. Shows 2 cards per row on tablet (500–800px)
3. Shows 3 cards per row on smaller desktop (800–1200px)
4. Shows 4 cards per row on large desktop (>1200px)
5. Has consistent spacing between cards

Use only grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) and gap. No media queries. Test by resizing the browser — the cards should reflow automatically at every width.

Task 2 — Holy grail layout

Build a page with the classic holy grail layout:

1. Header at the top (spans full width)
2. Sidebar on the left (fixed 250px)
3. Main content in the middle (flexible)
4. Footer at the bottom (spans full width)
5. 16px gap between all regions

Use grid-template-areas — write the layout as strings so the CSS mirrors the visual result. Then place children with grid-area. Test by adding content to each region — the layout should hold its shape regardless of content size.

Task 3 — Featured grid

Build a 3-column grid with 5 cards where:

1. The first card spans 2 columns and 2 rows (featured card)
2. The remaining 4 cards fill the empty cells
3. Consistent 16px gaps
4. Responsive — collapses to single column on mobile

Use grid-column: span 2 and grid-row: span 2 on the first card. For mobile, add a media query that changes grid-template-columns to 1fr and resets the featured card's spans.

⚠️ Common mistakes

The grid bugs that catch every beginner.

1. Forgetting display: grid

grid-template-columns without display: grid does nothing — the properties are ignored. Always set display: grid first.

2. Using grid for one-dimensional layouts

Using grid for a nav bar or button group is overkill. Flexbox is simpler and more appropriate for one-direction layouts. Use grid only when you need rows AND columns.

3. Forgetting gap between grid tracks

Without gap, grid cells touch each other. The result looks broken. Always add gap — usually 12-24px for cards, 16-32px for layout regions.

4. Using percentages instead of fr

33% 33% 33% with a 16px gap overflows by 32px. Fix: use 1fr 1fr 1fr. Grid's fr unit accounts for gaps automatically.

5. Setting height instead of min-height on layout containers

height: 400px on a grid with more content than fits clips the overflow. Fix: min-height: 400px — grows if content needs more, but has a minimum.

6. Confusing auto-fit with auto-fill

auto-fit collapses empty tracks and stretches remaining ones. auto-fill keeps them. Use auto-fit for card grids where you want cards to fill the row.

7. Not using minmax with auto-fit

repeat(auto-fit, 200px) gives fixed 200px tracks — never stretches. repeat(auto-fit, minmax(200px, 1fr)) gives 200px minimum AND stretches. Always pair auto-fit with minmax.

8. Forgetting that grid children can be grids

A grid child can itself be display: grid — nested grids are how you compose complex layouts. A page grid contains component grids. Both levels use the same properties.

➡️ What's next

You have the full layout toolkit. Next: making layouts adapt to any screen.

❓ Frequently asked questions

What is CSS Grid?

CSS Grid is a two-dimensional layout system. Unlike flexbox (which lays out items in one direction), grid lets you define rows AND columns at the same time. You define the grid with grid-template-columns and grid-template-rows on the parent, then place children into cells. It's the most powerful layout system ever shipped in CSS.

What is the difference between grid and flexbox?

Flexbox is one-dimensional (a row OR a column). Grid is two-dimensional (rows AND columns simultaneously). Use flexbox for component-level layout (nav bars, button groups). Use grid for page-level layout (dashboards, galleries, article pages). Modern sites often use both on the same page.

What does grid-template-columns do?

It defines the column structure of a grid. The value is a space-separated list of track sizes. grid-template-columns: 200px 200px 200px creates three fixed columns. 1fr 2fr 1fr creates three flexible columns where the middle is twice as wide. The fr unit means 'fraction of remaining space'.

What is the fr unit in CSS Grid?

fr stands for 'fraction'. It represents a fraction of the free space in the grid container. 1fr 1fr 1fr means three equal columns. 1fr 2fr 1fr means the middle column gets twice the space. It's different from percentages because percentages account for the whole container, while fr accounts for space AFTER gaps and fixed tracks.

What is auto-fit and auto-fill in CSS Grid?

Both create automatically responsive grids without media queries. auto-fill creates as many tracks as will fit, even if some are empty. auto-fit creates as many tracks as will fit, then collapses empty ones and lets remaining tracks stretch. Use auto-fit for card grids, auto-fill for icon galleries.

What does minmax() do in CSS Grid?

minmax(min, max) defines a track size with a minimum and maximum. minmax(200px, 1fr) means 'at least 200px, but grow to fill available space'. minmax(250px, 1fr) inside repeat(auto-fit, ...) is the classic responsive grid pattern.

How do I span multiple columns in CSS Grid?

Use grid-column on the child. grid-column: 1 / 3 means 'start at line 1, end at line 3' — spans two columns. grid-column: span 2 is shorthand for 'span two columns from wherever I am'. Same with grid-row for rows.

What is grid-template-areas?

grid-template-areas is a visual way to define grid layouts using names. You write the layout as strings: grid-template-areas: 'header header' 'sidebar main' 'footer footer'. Each string is a row, each name is a cell. Then place children with grid-area: header. The result is a layout you can read visually in the CSS.

🌐 External resources

← Back to CSS track
You're in the CSS track · Lesson 5