CSS Grid Basics

CSS · Lesson 27 of 55 7 min read Updated

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:

TermMeaning
Grid containerThe element with display: grid
Grid itemA direct child of the grid container
TrackOne row or one column
Grid lineThe dividing line between tracks, numbered from 1
CellThe space where one row meets one column
GapThe 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.

HTMLTry it yourself
<!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.

CSS
.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:

CSS
.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:

HTMLTry it yourself
<!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:

CSS
.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-items aligns items horizontally inside their cells (start, end, center, stretch).
  • align-items aligns items vertically inside their cells.
  • place-items: center sets both at once, which is the shortest way to centre something on a page.
  • justify-self and align-self do 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: grid on 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. Use 1fr instead.
  • A column that refuses to shrink. A 1fr track will not get smaller than its widest content, such as a long URL or a wide table. Use minmax(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: grid on a parent turns its direct children into grid items placed in rows and columns.
  • grid-template-columns and grid-template-rows define the tracks of the explicit grid.
  • The fr unit shares free space after gaps and fixed tracks, so it never overflows like percentages can.
  • repeat() shortens track lists, and minmax() 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
HTML
<!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?

2. Why is 1fr usually better than 33.33% for a three-column grid with gaps?

3. With only two items in a wide repeat(auto-fit, minmax(150px, 1fr)) grid, what happens?

4. Which property sizes rows that the browser creates automatically?

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