CSS Grid Basics
Flexbox is great for a single row or a single column. Many layouts, though, need rows and columns at the same time: a product catalogue, a photo gallery, a dashboard, a whole page. CSS Grid is the layout system built for that job.
In this lesson you will create a grid, size its columns with the fr unit, repeat() and minmax(), and build a card grid that adapts to any screen without a single media query.
What CSS Grid is
Grid is a two-dimensional layout system. You draw the rows and columns on the parent, and the children fall into the cells. With Flexbox, the items decide their sizes and the layout follows. With Grid, the layout comes first and the items fit into it.
A few words you will see often:
| Term | Meaning |
|---|---|
| Grid container | The element with display: grid |
| Grid item | A direct child of the grid container |
| Track | One row or one column |
| Grid line | The dividing line between tracks, numbered from 1 |
| Cell | The space where one row meets one column |
| Gap | The space between tracks |
Your first grid
To make a grid, set display: grid on the parent and describe the columns with grid-template-columns. Each value you list creates one column.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>First grid</title>
<style>
body { font-family: system-ui, sans-serif; }
.scores {
display: grid;
grid-template-columns: 150px 1fr 1fr;
gap: 8px;
}
.scores div {
padding: 12px;
background: #dbeafe;
border-radius: 6px;
}
</style>
</head>
<body>
<div class="scores">
<div>Team</div>
<div>Runs</div>
<div>Wickets</div>
<div>Lucknow</div>
<div>184</div>
<div>6</div>
<div>Kochi</div>
<div>179</div>
<div>8</div>
</div>
</body>
</html>The grid has three columns: the first is a fixed 150px and the other two share the remaining space. You did not say anything about rows. The browser created a new row every time the three columns filled up. gap adds space between rows and columns, but never around the outside edge.
The fr unit
fr stands for fraction of the free space. After fixed sizes and gaps are taken out, the rest is divided by the total number of fr units.
.layout {
display: grid;
grid-template-columns: 1fr 2fr; /* one third and two thirds */
gap: 16px;
}Why not use 33% and 67%? Because percentages ignore the gap. Two columns of 33% and 67% plus a 16px gap add up to more than 100%, so the grid overflows its box. fr units share only the space that is actually left, so they always fit.
repeat() and minmax()
Writing 1fr 1fr 1fr 1fr gets tiring. repeat() takes a count and a track size:
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 160px; /* every row is 160px tall */
gap: 12px;
}minmax() gives a track a smallest and a largest size. For example, minmax(200px, 1fr) means "never narrower than 200px, but grow to share the free space". Combining it with repeat() is where Grid becomes powerful.
Responsive grids without media queries
Instead of a fixed number of columns, you can ask the browser to fit as many columns as it can. Use auto-fit as the count:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Auto-fit cards</title>
<style>
body { font-family: system-ui, sans-serif; margin: 16px; }
.products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
gap: 16px;
}
.card {
padding: 16px;
border: 1px solid #d4d4d8;
border-radius: 10px;
}
.card h2 { margin: 0 0 4px; font-size: 1.1rem; }
.card p { margin: 0; color: #15803d; font-weight: bold; }
</style>
</head>
<body>
<div class="products">
<article class="card"><h2>Cricket bat</h2><p>₹1,299</p></article>
<article class="card"><h2>Tennis ball pack</h2><p>₹240</p></article>
<article class="card"><h2>Batting gloves</h2><p>₹899</p></article>
<article class="card"><h2>Kit bag</h2><p>₹1,050</p></article>
<article class="card"><h2>Stumps set</h2><p>₹650</p></article>
</div>
</body>
</html>Open the example and drag the editor wider and narrower. On a wide screen you get four or five columns; on a phone, one. Here is what the long line means:
repeat(auto-fit, ...)creates as many columns as will fit.minmax(..., 1fr)lets each column grow to share leftover space.min(100%, 180px)sets the smallest column to 180px, or to the full width if the screen is narrower than 180px, so a tiny screen never overflows.
There is also auto-fill. The difference only shows when there are fewer items than columns. auto-fill keeps the empty columns, so two cards stay narrow on a wide screen. auto-fit collapses the empty columns, so the two cards stretch to fill the row. For card layouts, auto-fit is usually what you want.
Rows and the implicit grid
Columns you define with grid-template-columns and rows you define with grid-template-rows form the explicit grid. When there are more items than cells, the browser adds extra rows automatically. These are part of the implicit grid, and you size them with grid-auto-rows:
.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(100px, auto); /* at least 100px, taller if content needs it */
}By default items flow into the grid row by row. grid-auto-flow: column makes them fill column by column instead, and grid-auto-flow: dense lets later small items fill gaps left by bigger ones.
Aligning grid items
Grid uses the same alignment words as Flexbox:
justify-itemsaligns items horizontally inside their cells (start,end,center,stretch).align-itemsaligns items vertically inside their cells.place-items: centersets both at once, which is the shortest way to centre something on a page.justify-selfandalign-selfdo the same for one item.
By default, items stretch to fill their cell, which is why all the cards in a row are the same height.
Common mistakes
- Putting
display: gridon the wrong element. It goes on the parent. Only direct children become grid items. - Using percentages with gaps.
repeat(3, 33.33%)plus gaps overflows. Use1frinstead. - A column that refuses to shrink. A
1frtrack will not get smaller than its widest content, such as a long URL or a wide table. Useminmax(0, 1fr)when that is a problem. - Forgetting
min()in auto-fit grids.minmax(250px, 1fr)overflows on screens narrower than 250px.minmax(min(100%, 250px), 1fr)does not.
Key points
display: gridon a parent turns its direct children into grid items placed in rows and columns.grid-template-columnsandgrid-template-rowsdefine the tracks of the explicit grid.- The
frunit shares free space after gaps and fixed tracks, so it never overflows like percentages can. repeat()shortens track lists, andminmax()sets a smallest and largest track size.repeat(auto-fit, minmax(min(100%, 200px), 1fr))makes a responsive grid with no media queries.- Extra rows are created automatically; size them with
grid-auto-rows.
Exercise
Meera runs a sweet shop in Jaipur and wants a menu grid. Create a grid of six sweets, each in a box with a name and a price. On wide screens the boxes should sit in as many columns as fit, each at least 160px wide. Every row should be at least 120px tall, and the gap should be 12px. Centre the text inside each box.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sweet menu</title>
<style>
.menu {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr));
grid-auto-rows: minmax(120px, auto);
gap: 12px;
}
.menu div {
display: grid;
place-items: center;
background: #fef3c7;
text-align: center;
}
</style>
</head>
<body>
<div class="menu">
<div>Kaju katli<br>₹1,100/kg</div>
<div>Ghevar<br>₹640/kg</div>
<div>Rasgulla<br>₹360/kg</div>
<div>Jalebi<br>₹300/kg</div>
<div>Besan ladoo<br>₹480/kg</div>
<div>Mysore pak<br>₹720/kg</div>
</div>
</body>
</html>auto-fit with minmax() decides the number of columns, grid-auto-rows sets the row height, and each box is itself a small grid so place-items: center can centre its text.
Quick Quiz
1. What does grid-template-columns: 200px 1fr 1fr create?
Each value in grid-template-columns creates one column, and the two 1fr columns split the remaining space.
2. Why is 1fr usually better than 33.33% for a three-column grid with gaps?
Percentage tracks ignore the gaps, so three of them plus two gaps add up to more than 100%.
3. With only two items in a wide repeat(auto-fit, minmax(150px, 1fr)) grid, what happens?
auto-fit collapses empty tracks, so the remaining items grow. auto-fill would keep the empty columns.
4. Which property sizes rows that the browser creates automatically?
Automatically created rows belong to the implicit grid, and grid-auto-rows controls their size.
Learn CSS with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
