Lesson 13 of 25

CSS Grid

Rows and Columns at the Same Time

Grid is the only layout system in CSS that controls both directions at once. You describe a set of columns and a set of rows on the container, and items are placed into the cells that result. Because the structure is defined up front, items line up across and down — something flexbox cannot guarantee, since each of its lines is sized independently.

Like flexbox, it starts with one declaration on the parent: display: grid. The direct children become grid items. What you add next is a description of the tracks — grid-template-columns for the vertical divisions and grid-template-rows for the horizontal ones — and gap for the space between them.

The unit you will use constantly is fr, short for fraction. It represents a share of the space left after any fixed sizes and gaps are accounted for. grid-template-columns: 1fr 1fr 1fr means three equal columns. 250px 1fr means a fixed 250-pixel sidebar and a main area that takes everything else. Unlike percentages, fr already accounts for the gaps, so three 1fr columns with a 24-pixel gap fit perfectly — while 33.33% three times plus gaps overflows.

Example
/* Three equal columns */
.gallery {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 24px;
}

/* repeat() says the same thing more briefly */
.gallery { grid-template-columns: repeat(3, 1fr); }

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

/* Mixed units are fine, and fr accounts for the gaps */
.mixed { grid-template-columns: 200px 2fr 1fr; }

/* Rows can be sized too; unlisted rows take their content height */
.page {
  display: grid;
  grid-template-rows: auto 1fr auto;   /* header, body, footer */
  min-height: 100vh;
}
  • display: grid goes on the container; direct children become grid items
  • fr is a share of the remaining space, calculated after gaps and fixed tracks
  • repeat(3, 1fr) is shorthand for writing 1fr three times
  • auto sizes a track to its content
  • gap — or row-gap and column-gap separately — spaces the tracks
  • Rows you never declared are created automatically and sized by grid-auto-rows

Responsive Without a Single Media Query

One line of Grid replaces what used to take three breakpoints. grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) tells the browser: make as many columns as will fit, where each one is at least 250 pixels wide and shares the leftover space equally. On a wide screen that might be four columns; on a tablet, two; on a phone, one. Nothing in your CSS mentions a screen size, and the layout adapts to the container it is actually in.

minmax(min, max) is the part doing the work. It sets a floor and a ceiling for a track, so the column can flex between them instead of being one fixed size. Used with auto-fit or auto-fill, it lets the browser calculate how many columns fit.

The two auto- keywords differ only when there are fewer items than fit in the row, and the difference is visible. auto-fill creates the empty columns anyway, so two cards on a wide screen stay 250 pixels wide with a large gap after them. auto-fit collapses the empty tracks, so those two cards stretch to fill the whole row. For card galleries, auto-fit is usually what people want; auto-fill is right when the column rhythm should stay consistent even when half-empty.

One subtlety saves a debugging session later: a track written as 1fr is really minmax(auto, 1fr), and that automatic minimum stops the track shrinking below its content. A long unbroken string inside a grid item can therefore push a column wider than its share. Writing minmax(0, 1fr) instead removes the floor and lets the track shrink properly — the same idea as min-width: 0 on a flex item.

Example
/* A responsive gallery with no breakpoints at all */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 24px;
}

/* auto-fill keeps the empty columns; auto-fit collapses them */
.consistent { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
.stretchy   { grid-template-columns: repeat(auto-fit,  minmax(250px, 1fr)); }

/* Stop a long word forcing a column wider than its share */
.safe { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Rows created on the fly get this height */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  grid-auto-rows: 180px;
  gap: 12px;
}
Notes
  • minmax(250px, 1fr) has one failure mode: on a screen narrower than 250 pixels plus padding, the track cannot honour its minimum and the grid overflows. minmax(min(250px, 100%), 1fr) avoids it by letting the minimum fall back to the container width on very small screens.

Placing Items: Line Numbers and Named Areas

Items fill the grid in source order by default, one cell at a time. When you want something to occupy a specific place — a hero tile spanning two columns, a sidebar running down three rows — you place it explicitly.

The first way is by line number. Grid lines are the dividers between tracks, numbered from 1 starting at the left edge, so three columns have four lines. grid-column: 1 / 3 means "start at line 1, end at line 3", which covers two columns. If counting the far edge is awkward, negative numbers count backwards from the end: -1 is always the last line, so grid-column: 1 / -1 means "span the full width" however many columns there are. span 2 is another way to say the same thing without naming an end line.

The second way is far more readable for a page skeleton. grid-template-areas lets you draw the layout as text, one quoted string per row, using names you invent. Each item then says which area it belongs to with grid-area. Anyone reading the CSS can see the shape of the page at a glance, and rearranging it for mobile is a matter of rewriting a few short strings inside a media query. Two rules apply: every row string must have the same number of names, and each named area must form a rectangle. Use a full stop for a cell you want to leave empty.

Example
/* By line number */
.gallery .featured {
  grid-column: 1 / 3;    /* from line 1 to line 3 — two columns */
  grid-row: 1 / 3;       /* and two rows tall */
}

.full-width { grid-column: 1 / -1; }   /* edge to edge, any column count */
.two-wide   { grid-column: span 2; }   /* two columns from wherever it lands */

/* By named area — the layout is readable as text */
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  gap: 24px;
  min-height: 100vh;
}

.page > header  { grid-area: header; }
.page > .side   { grid-area: sidebar; }
.page > main    { grid-area: main; }
.page > footer  { grid-area: footer; }

/* Rearranging for phones is a rewrite of three short strings */
@media (max-width: 767px) {
  .page {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "footer";
  }
}
  • Grid lines are numbered from 1; three columns give you four lines
  • grid-column: 1 / -1 spans the full width whatever the column count
  • span 2 spans two tracks from wherever the item is placed
  • Every row string in grid-template-areas needs the same number of names
  • Each named area must be a rectangle; use . for a deliberately empty cell
  • Placing items out of source order moves them visually but not for keyboard users

Aligning in Grid, and Choosing Between Grid and Flexbox

Grid alignment uses the same four property names as flexbox but with one very welcome simplification: they never swap. In Grid, justify- always refers to the horizontal, column direction and align- always refers to the vertical, row direction, regardless of anything else. justify-items and align-items position content inside each cell; justify-content and align-content position the whole grid inside the container when the tracks do not fill it. place-items: center is a shorthand that sets both item properties at once, and it is the shortest way in CSS to centre something in both directions.

So when do you use which? The honest answer is that they are complementary and most real pages use both. Reach for Grid when you know the structure you want and are placing content into it: a page skeleton, a card gallery, a form with aligned labels, a dashboard. Reach for Flexbox when the content decides the layout and you only care about one direction: a navigation bar, a row of buttons, a media object with an avatar beside some text, anything that should size itself to what is in it.

A useful way to phrase it: Grid is layout-first, Flexbox is content-first. And they nest happily — a Grid page skeleton whose header is a Flex row, containing cards that are themselves Flex columns, is an entirely ordinary and sensible structure.

One last practical note. Grid items stretch to fill their cell by default, which is why cards in a grid come out the same height automatically. If a card's own content should be pushed apart — a title at the top and a button pinned to the bottom, whatever the text length — make the card a flex column with justify-content: space-between. That combination is behind an enormous number of tidy card designs.

Example
/* Centre something, in both directions, in two lines */
.centre {
  display: grid;
  place-items: center;
  min-height: 60vh;
}

/* Inside each cell vs the grid as a whole */
.grid {
  justify-items: start;      /* content sits at the left of its cell */
  align-items: center;       /* and vertically centred in its cell */
  justify-content: center;   /* the whole grid centred in the container */
}

/* Grid for the structure, Flex inside each card */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 24px;
}

.card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;   /* button sticks to the bottom */
}
A page skeleton and a responsive gallery
HTML
<h3>Named areas — a whole page layout</h3>
<div class="page">
  <header>header</header>
  <aside class="side">sidebar</aside>
  <main>main content</main>
  <footer>footer</footer>
</div>

<h3>auto-fit + minmax — resize the preview and count the columns</h3>
<div class="gallery">
  <div class="tile featured">featured<br><small>spans 2 columns</small></div>
  <div class="tile">2</div>
  <div class="tile">3</div>
  <div class="tile">4</div>
  <div class="tile">5</div>
  <div class="tile">6</div>
</div>
CSS
body {
  font-family: system-ui, Arial, sans-serif;
  padding: 24px;
  background: #f4f4f7;
}

h3 { font-size: 15px; color: #555555; margin: 24px 0 8px; }

/* --- 1. Named areas --- */
.page {
  display: grid;
  grid-template-columns: 160px 1fr;
  grid-template-rows: auto 200px auto;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  gap: 8px;
}

.page > * {
  background: #ffffff;
  border: 1px solid #e3e3ea;
  border-radius: 8px;
  padding: 12px;
  color: #555555;
}

.page > header  { grid-area: header;  background: #d1039e; color: #fff; border: 0; }
.page > .side   { grid-area: sidebar; }
.page > main    { grid-area: main; }
.page > footer  { grid-area: footer;  background: #7a0060; color: #fff; border: 0; }

/* Rewrite the picture for narrow screens */
@media (max-width: 600px) {
  .page {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto auto;
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "footer";
  }
}

/* --- 2. Responsive gallery, no media query --- */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-top: 8px;
}

.tile {
  background: #ffffff;
  border: 1px solid #e3e3ea;
  border-radius: 8px;
  padding: 24px 12px;
  text-align: center;
  color: #555555;
}

.featured {
  grid-column: span 2;   /* try 1 / -1 to make it full width */
  background: #fdf2fa;
  border-color: #d1039e;
  color: #7a0060;
}
Notes
  • Turn on the grid overlay in your browser's developer tools — in the Elements panel a small grid badge appears next to any grid container. It draws the track lines and their numbers directly on the page, which makes grid-column: 2 / 4 obvious instead of something you count out on your fingers.
Ask AI