Skip to main content
ANVISoftware Solutions
Lesson 8 of 20Beginner17 min

CSS Grid

By the end of this lesson

Build two-dimensional layouts with rows and columns.

Grid lays elements out in two directions at once. You describe the columns and rows on the container, and the children take their places in them.

The practical difference from flexbox: with grid, the tracks exist before the content does. Every card in the third column is the same width because the column has a width, not because the cards happen to contain similar text.

Three equal columns of employee cards
CSS
.employee-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
  • display: grid makes this a grid container. Its direct children become grid items and are placed automatically, filling each row before starting the next.
  • grid-template-columns describes the columns. Here there are three.
  • 1fr means one share of the space left after gaps and fixed sizes are taken out. Three equal shares give three equal columns — and unlike 33.33%, the fr unit already accounts for the gaps.
  • repeat(3, 1fr) is shorthand for 1fr 1fr 1fr. With twelve columns you would notice the difference.
  • gap spaces both rows and columns. gap: 24px 16px sets them separately, rows first.

The pieces you will reach for most:

fr
A share of the free space in the grid. 2fr 1fr gives the first column twice the remaining space, after gaps and fixed tracks are subtracted.
repeat(n, size)
Repeats a track definition. Keeps long column lists readable.
minmax(min, max)
A track that will not go below the minimum or above the maximum. minmax(16rem, 1fr) means at least 16rem, then take a share of what is left.
auto-fit
Fit as many tracks as will go at the given minimum, then stretch them to fill the row. Empty tracks are collapsed, so the items you do have spread out.
auto-fill
Same counting, but empty tracks are kept. With two cards in a row that holds four, auto-fill leaves two card-sized gaps and auto-fit does not.
grid-template-areas
Names regions in a picture of the layout, so the structure is readable in the stylesheet rather than assembled from line numbers.
place-items
Alignment inside each cell, both axes at once. place-items: center is the shortest way to centre something in a box.
A card grid that responds with no media query
CSS
.employee-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 24px;
}
  • Read it as an instruction: fit as many columns as you can, none narrower than 16rem, and share out anything left over.
  • On a 1200px container that produces four columns. Around 800px it becomes three, then two, then one, at whatever widths the container actually reaches.
  • Nothing here mentions a screen size. The layout responds to the space the grid has, so it still behaves when the same component is dropped into a narrow sidebar — something a media query based on viewport width cannot do.
  • Choose the minimum from the content: what is the narrowest an employee card can be and still be readable? That number is the breakpoint, and it is a property of the card rather than of any device.

Both are layout tools and both use gap, so the choice is not obvious from the outside. The question to ask is whether the layout or the content should decide the sizes:

 FlexboxGrid
DimensionsOne axis — a row or a columnTwo axes — rows and columns together
What decides the sizesThe content. Items take their natural size, then grow or shrinkThe layout. You define the tracks, and items fit into them
Alignment between siblingsWithin one line only. Separate rows cannot line up with each otherAcross the whole grid. Column three is the same width in every row
Typical useToolbars, button groups, a label and a value, card footersPage layouts, card galleries, data-style tables of blocks, forms with aligned columns
Handling unknown item countsflex-wrap moves overflow onto new lines, sized by contentauto-fit with minmax creates as many equal tracks as fit
Placing one specific itemAwkward — you can only reorder within the lineDirect — name an area or give it explicit start and end lines
Named areas for the directory page
CSS
.directory-page {
  display: grid;
  gap: 24px;
  grid-template-areas:
    "filters"
    "results"
    "pagination";
}

.directory-filters {
  grid-area: filters;
}
.directory-results {
  grid-area: results;
}
.directory-pagination {
  grid-area: pagination;
}

@media (min-width: 48rem) {
  .directory-page {
    grid-template-columns: 15rem 1fr;
    grid-template-areas:
      "filters results"
      "filters pagination";
  }
}
  • The quoted strings are a picture of the layout. Each string is a row, and each name inside it is a column.
  • On a narrow screen the three regions stack. Each child says which area it belongs to, and nothing depends on its position in the markup.
  • Above 48rem the picture changes to two columns, with filters spanning both rows because its name appears twice.
  • This is the readable part: someone can see the layout in the stylesheet without mentally reconstructing it from line numbers.
  • The markup order stays the same at both sizes, so the reading and tab order are unchanged. Grid can move things visually, and moving them a long way from their document order makes a page confusing to navigate by keyboard.

Summary

  • Grid defines rows and columns on the container, so tracks exist independently of the content
  • fr distributes free space and already accounts for gaps, unlike percentages
  • repeat(auto-fit, minmax(...)) gives a responsive card grid with no media queries
  • Named areas make the layout readable and survive tracks being added later
  • Flexbox for one axis driven by content, grid for two axes driven by the layout — and a real table for real tabular data

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

One grid, no breakpoints

Build a gallery of employee cards that shows four across on a wide monitor, two on a tablet and one on a phone, without writing a single media query.

Then resize the browser slowly and watch where it changes. Does it change at a sensible width for the content?

Show solution

auto-fit with minmax does the whole job. The minimum track size is the only number you choose, and you choose it by asking how narrow a card can get before it reads badly.

The column count changes based on the container's width, not the viewport's. That is usually what you actually wanted, and it means the component keeps working if it is later placed in a narrower column.

The trade-off: you give up exact control of the count at each size. When a design specifies precisely three across at a given width, a media query is the honest way to express that.

CSS
.employee-grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.employee-card {
  display: grid;
  gap: 0.25rem;
  align-content: start;
  padding: 1rem;
  border: 1px solid #d8dde3;
  border-radius: 0.5rem;
}

Think about it

Think about it

An expense list has a date, description, category and amount per row. Would you build it with flexbox or grid, and what changes your answer?

Show solution

If each row only needs to look tidy in itself, flexbox is enough and simpler.

If the columns must line up down the whole list — dates under dates, amounts under amounts — grid is the better fit, because a flex row measures its own content and knows nothing about the row above it.

There is a third answer worth stating: genuine tabular data belongs in a table element. A table gives row and column semantics to assistive technology, and grid does not. Use grid for layout, and a table when the data really is a table.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

What does repeat(auto-fit, minmax(16rem, 1fr)) do?
You need the third column to be the same width in every row of a card gallery. Which tool fits?

Saved in this browser only.