Learn CSS · Lesson 7 of 11 · 12 min

CSS Grid

Widely availablesince Apr 2020 Widely availablesince Mar 2026

Flexbox arranges one line. Grid arranges rows and columns at the same time, so things line up in both directions. It looks like a lot of syntax, and most of it turns out to be six nouns. Learn the nouns and the properties read like sentences.

Six words

Line

The dividers, numbered from 1. Three columns have four column lines. Items are placed between lines, and -1 always means the last one.

Track

The space between two neighbouring lines: a whole column or a whole row. grid-template-columns is a list of track sizes.

Cell

One column crossing one row. The smallest unit, and what an item occupies when you give it no instructions.

Area

A rectangle of cells. Any item can cover an area, and an area can be given a name.

Gap

The gutter between tracks. It exists only between them, never around the outside of the grid.

Implicit track

A row or column you never declared, which the grid adds because there were more items than cells. Most rows on the web are implicit.

Draw a layout, get the CSS

Pick a name, then click or drag across the cells to paint it. The strings underneath are what grid-template-areas takes, and the preview on the right is laid out by them.

On the container

six cards · they go on the parent
1

Columns that share the space

.layout {
  display: grid;
  grid-template-columns: 200px 1fr 2fr;
}

Goes on: the container.

One value per column. fr is a share of what is left after the fixed tracks and the gaps are taken out, so here the last column is twice the middle one and the first never changes.

What it replaced: percentages that had to add up to 100 minus the gutters, worked out by hand.

2

As many columns as fit

.cards {
  display: grid;
  grid-template-columns:
    repeat(auto-fit, minmax(min(100%, 140px), 1fr));
}

Goes on: the container.

Fit as many 140px columns as the container allows, then stretch them to fill the row. The column count changes by itself as the container changes, with no media query. The min(100%, …) keeps a single column from overflowing a container narrower than 140px. Drag the corner of the figure.

What it replaced: three or four breakpoints, each resetting a width and an nth-child margin.

3

Gutters

.cards {
  gap: 24px 12px;   /* rows, then columns */
}

Goes on: the container.

The same property as in flexbox. Two values set the row gap and the column gap separately.

4

Name the regions

.page {
  display: grid;
  grid-template-columns: 180px 1fr;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
}

Goes on: the container.

Each string is a row and each word is a cell. Repeating a name makes the area bigger, and a full stop leaves a cell empty. Every area has to be a rectangle. If one is not, the browser throws away the whole declaration. The widget above writes this for you.

5

Size the rows you never declared

.cards {
  grid-auto-rows: minmax(90px, auto);
}

Goes on: the container.

Items past the end of your template land in implicit rows, which are sized to their content unless you say otherwise. This gives every such row a floor of 90px and still lets it grow for long content.

What it replaced: a min-height on every card.

6

Align what is inside every cell

.icons {
  display: grid;
  place-items: center;
}

Goes on: the container.

Shorthand for align-items and justify-items. Items are stretched to fill their cell unless told otherwise. This centres each one in its own cell, both ways. On a grid with a single cell it is the shortest way to centre anything.

On an item

four cards · they go on a child
7

Cover more than one cell

.feature {
  grid-column: span 2;
  grid-row: span 2;
}

Goes on: the item.

span counts tracks from wherever the item lands. The other items flow around it, and you did not have to touch them.

What it replaced: a table with colspan and rowspan.

8

Place by line number

.banner {
  grid-column: 1 / -1;
}

Goes on: the item.

From the first column line to the last. Negative numbers count from the end, so this is full width however many columns the grid has at the moment. It only counts columns you declared, not implicit ones.

9

Drop an item into a named area

.site-header {
  grid-area: header;
}

Goes on: the item.

The other half of card four. The HTML order no longer decides where a region sits, so one media query that rewrites the template strings can rearrange the whole page.

Careful. As with order in flexbox, this moves pixels only. Keep the HTML in the order it should be read.

10

Line up the insides of separate cards

Widely available

A grid only aligns its direct children. Three pricing cards are three separate boxes, so a long title in one pushes its button lower than its neighbours’. With subgrid a card borrows its parent’s row lines for its own children, and titles, text and buttons line up across all three cards.

Starter

One short line.

Choose

A much longer plan name that wraps

This description runs to several lines, which used to push this card’s button out of line with its neighbours.

Choose

Team

Two lines of text here, a little more than the first.

Choose
.cards { display: grid; grid-template-columns: repeat(3, 1fr); }
.card  { display: grid; grid-row: span 3; grid-template-rows: subgrid; }
Each card spans three of the parent’s rows and hands those rows to its own title, text and button. Full reference and who ships it →

Goes on: the item, which must itself be a grid.

What about masonry?

A layout where items pack upwards into the shortest column, like a wall of pins. CSS has a draft for it, and the browser dataset this site reads at build time has no entry for it at all, which is an answer in itself: do not plan a layout around it yet. Use card two, which gives an even wall with aligned rows, or CSS columns if only the vertical packing matters and reading order does not. When the dataset gains an entry, this paragraph will change by itself.

Practice

  1. In the widget above, paint a layout where aside sits to the right of main and the footer is full width. Then break it on purpose: make main an L shape and read the message.
  2. Open the Bento Grid Builder, export a layout, and find cards one, three and seven in the CSS it gives you.
  3. Open DevTools on any grid and click the grid badge in the Elements panel. The browser draws every line with its number, positive and negative. Count them against the first of the six words.

Check yourself

A grid has four columns. How many column lines does it have, and what number is the last one?

Five. The last is line 5, and it is also line -1, which is why grid-column: 1 / -1 keeps meaning “full width” when the column count changes.

You wrote grid-template-rows for two rows and added seven items to a three-column grid. What sizes the third row?

Nothing you wrote. It is an implicit row, sized to its content, unless you set grid-auto-rows (card five).

Your grid-template-areas is being ignored completely. What is the likeliest cause?

One of the names does not form a rectangle, or the rows have different numbers of cells. Either makes the whole declaration invalid, and the browser drops it without a message. DevTools shows it struck through.

The mental model to keep

The container draws lines. Items sit between lines. Everything else, fr, repeat, minmax, areas, span, is a shorter way of saying where the lines are or which ones an item sits between. Use grid for the skeleton of a page and anything that must align in two directions, and flexbox inside the cells for the single rows of things. Next: centering, decided.

Last reviewed 2026-09-18. Browser status on this page is read from the web-features dataset when the site is built, not typed. The page prints as its cards.