C.W.K.
Stream
Lesson 01 of 04 · published

Grid's Mental Model: Rows AND Columns

~12 min · grid, tracks, lines, fr, repeat, minmax

Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Grid is a coordinate system. Define the tracks; the items snap into cells. Think in 2D from the start."

The Grid Vocabulary

  • Track — a row or a column. A grid has row tracks and column tracks.
  • Line — the boundary between (or around) tracks. Lines are numbered starting from 1.
  • Cell — the intersection of one row and one column. The smallest unit of placement.
  • Area — a rectangle of one or more cells. An item can occupy an area.
  • Item — a direct child of the grid container; it occupies one or more cells.

Declaring Tracks

You define tracks with grid-template-columns and grid-template-rows. Each value in the list is one track's size:

  • grid-template-columns: 100px 200px 300px — three columns of those exact widths.
  • grid-template-columns: 1fr 2fr 1fr — three columns sharing space in 1:2:1 ratio. fr means "fractional unit of remaining space."
  • grid-template-columns: 240px 1fr — fixed sidebar + flexible main. The classic two-column app shell.
  • grid-template-columns: repeat(3, 1fr) — three equal columns. repeat(N, value) repeats a track definition N times.
  • grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) — as many 200px+ columns as fit, each expanding to fill remaining space. The responsive grid one-liner.

The fr Unit

fr = fractional. It distributes remaining space (after fixed-size tracks are accounted for) in the proportions you specify. 1fr 1fr = 50/50. 2fr 1fr 1fr = 50/25/25. 240px 1fr = sidebar 240px, main gets the rest. fr works on the container's content area; fixed-size tracks subtract first, then fr divides what's left.

The minmax() Function

minmax(100px, 1fr) = "at least 100px, at most 1fr". This is the key to responsive grids — columns stay readable even when the container shrinks, but they expand to fill extra space when the container is wide.

The repeat() Function

  • repeat(3, 1fr) — exactly three equal tracks.
  • repeat(auto-fill, 200px) — as many 200px tracks as fit; tracks stay 200px wide. Empty tracks remain (reserve space).
  • repeat(auto-fit, 200px) — same, but empty tracks collapse and existing items expand to fill.
  • repeat(auto-fit, minmax(200px, 1fr)) — the responsive grid. Cards are at least 200px; if container has room for 4, you get 4 columns; if only 2 fit, the cards expand to fill the row.

auto-fit vs auto-fill: identical when the grid has enough items to fill the tracks. When items are fewer than tracks: auto-fill leaves empty space (preserving the grid shape); auto-fit collapses empty tracks and stretches existing items to fill the row. Almost everyone wants auto-fit for content cards.

Implicit Tracks

If you have more items than the explicit grid has cells, Grid creates additional rows (or columns) automatically. The size of these implicit tracks is controlled by grid-auto-rows / grid-auto-columns. Default is auto (sized by content).

Gap

gap: 1rem on a grid container puts 1rem of space between every row and every column. row-gap: 2rem; column-gap: 1rem sets them independently. Same property name as Flexbox; same semantics.

Placing Items

Items snap into cells automatically in source order (auto-placement, next lesson covers details). To override, use grid-column and grid-row:

  • grid-column: 1 / 3 — span from column line 1 to column line 3 (= two cells).
  • grid-column: 1 / -1 — span from line 1 to the last line. The -1 always means "the last explicit line." Useful for full-width header in a multi-column grid.
  • grid-column: span 2 — span 2 columns, wherever auto-placement puts the item.
  • grid-area: 2 / 1 / 4 / 3 — row-start / column-start / row-end / column-end. Four values, full placement.
fr + minmax + auto-fit are the responsive grid trinity. grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) replaces 80% of media queries for card-based layouts. One declaration, infinite responsive behavior.

Pippa's Note

The cwk-quests catalog page is a single line: grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)). On a 1600px monitor it shows 5 quest cards per row; on a 768px tablet, 2-3; on a phone, 1. Zero media queries for the grid itself. The quest content pages use named areas (next lesson) for the sidebar + main + meta layout. Two patterns, dozens of pages.

Code

Three grid patterns that cover 80% of usage·css
/* The minimal grid */
.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;   /* three equal columns */
  gap: 1rem;
}
/* No grid-template-rows: rows are implicit, sized by content */

/* The two-column app shell — fixed sidebar + flexible main */
.shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
}

/* The responsive card grid — the most-copied grid one-liner */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
}
/* Cards are at least 240px wide; as many fit per row as the container allows;
   they grow to fill the row when fewer fit. Zero media queries. */
Placement with line numbers and span·css
/* Placing items explicitly */

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: 1rem;
  min-height: 100vh;
}

.header {
  grid-column: 1 / -1;       /* full width: line 1 to last line */
  grid-row: 1;                /* first row */
}

.sidebar {
  grid-column: 1 / 2;        /* first column */
  grid-row: 2 / 3;            /* second row */
}

.main {
  grid-column: 2 / -1;       /* columns 2 to end */
  grid-row: 2 / 3;
}

.footer {
  grid-column: 1 / -1;       /* full width */
  grid-row: 3;                /* third row */
}

/* Shorthand for placement: grid-area: row-start / col-start / row-end / col-end */
.featured-card {
  grid-area: 1 / 1 / 3 / 3;  /* spans rows 1-2, columns 1-2 (a 2x2 area) */
}

/* Span syntax */
.tall-card {
  grid-row: span 2;          /* takes 2 rows */
}
Dashboard + magazine — fr/minmax/repeat at work·css
/* fr, minmax, repeat in combination */

/* A dashboard with sidebar, two-column content, no media queries */
.dashboard {
  display: grid;
  grid-template-columns:
    minmax(200px, 240px)              /* sidebar: between 200 and 240 */
    repeat(auto-fit, minmax(280px, 1fr)); /* tiles: responsive */
  gap: 1.5rem;
  padding: 1.5rem;
}

/* A magazine-style layout */
.magazine {
  display: grid;
  grid-template-columns: repeat(12, 1fr);  /* 12-column grid like Bootstrap */
  gap: 1rem;
}
.magazine .lead { grid-column: 1 / 8; }    /* 7-column lead article */
.magazine .aside { grid-column: 8 / 13; }  /* 5-column aside */
.magazine .footer { grid-column: 1 / -1; } /* full-width footer */

External links

Exercise

Build three layouts: (1) a responsive card grid with grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)) and at least 8 cards — resize the window and watch the columns reflow; (2) a two-column app shell with a 240px sidebar and a flexible main, with a full-width header that spans both columns; (3) a 12-column magazine grid where the lead article is 7 columns wide and an aside is 5 columns. Inspect each with the DevTools grid overlay.
Hint
For #1, count the columns as you resize: at 1200px, how many fit? At 800px? At 400px? The math is (container_width + gap) / (column_min + gap) rounded down. Once you see the math, the layout feels less magical.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.