Deep dive: Grid

Chapter 6 — Deep Dive: Grid

CSS Grid is a two-dimensional layout system — it places items on both rows and columns simultaneously. While Flexbox distributes items along a single axis, Grid lets you design the layout first and place content into it. It is the right tool for page-level structure, dashboard layouts, card grids, and anything that benefits from alignment across two dimensions at once.

1. Grid Lines and Tracks

A grid is defined by its lines. Lines are numbered starting at 1 from the start edge. Between two adjacent lines is a track (column track or row track). The area bounded by four lines is a cell. An item can span multiple cells to form a grid area.

line 1 line 2 line 3 line 4 line 5
col 1 · row 1
col 2 · row 1
col 3 · row 1
col 4 · row 1
col 1 · row 2
col-start 2
row-start 2
col-end 4
row-end 3
col 4 · row 2
← column track 1 → ← column track 2 → ← column track 3 → ← column track 4 →
Blue item: grid-column: 2 / 4 · grid-row: 2 / 3 — spans from line 2 to line 4 horizontally, line 2 to line 3 vertically. Negative line numbers count from the end: grid-column: 1 / -1 spans the full width regardless of column count.

2. Defining the Grid — Container Properties

PropertyValuesNotes
Track definition
grid-template-columns length · fr · auto · min-content · max-content · repeat() · minmax() Defines the explicit column tracks. Space-separated list. The most important grid property.
grid-template-rows same as columns Defines the explicit row tracks. Often left unset — rows grow to content height by default.
grid-template-areas quoted strings of area names Names regions of the grid visually. Each string is a row; each word is a cell. Use . for empty cells. Makes layouts readable at a glance.
grid-template rows / columns Shorthand for template-rows + template-columns + template-areas. Avoid — overly terse for most team codebases.
Implicit grid (auto-generated tracks)
grid-auto-columns length · fr · auto · minmax() Size of implicitly created column tracks (when items are placed outside the explicit grid).
grid-auto-rows length · fr · auto · minmax() Size of implicitly created row tracks. grid-auto-rows: minmax(100px, auto) is a common pattern for variable-height rows with a minimum.
grid-auto-flow row · column · dense · row dense · column dense row (default) — items fill rows left to right. column — fills columns top to bottom. dense — backfills gaps with smaller items (changes visual order — accessibility concern).
Gaps and alignment
gap / row-gap / column-gap length · percentage Same as flexbox. gap: 16px 24px sets row-gap 16px, column-gap 24px.
justify-items stretch · start · end · center Aligns items inline (horizontally) within their grid cell. Default stretch. Unlike flexbox, this property is meaningful in Grid.
align-items stretch · start · end · center · baseline Aligns items block (vertically) within their grid cell.
justify-content start · end · center · stretch · space-between · space-around · space-evenly Distributes the tracks themselves within the grid container when tracks are smaller than the container. Rarely needed with fr units (fr absorbs all space).
align-content same as justify-content Distributes row tracks vertically. Only applies when the total track height is less than the container height.

3. Track Sizing — fr, minmax(), repeat()

The fr unit

/* fr = fraction of remaining space after fixed tracks are placed */ .grid { grid-template-columns: 200px 1fr; /* sidebar: fixed 200px. main: gets ALL remaining space */ } .grid { grid-template-columns: 1fr 2fr 1fr; /* total 4 parts: left=25%, centre=50%, right=25% */ } .grid { grid-template-columns: 200px 1fr 160px; /* left sidebar 200px, right sidebar 160px, middle gets everything left over */ } /* fr vs % — fr is better because it accounts for gap */ /* 1fr 1fr with gap: 20px → each column = (container - 20px) / 2 */ /* 50% 50% with gap: 20px → total = container + 20px → overflow */

minmax()

/* minmax(minimum, maximum) — a track can't be smaller than min or larger than max */ .grid { grid-template-columns: minmax(200px, 1fr) minmax(200px, 1fr); /* each column: at least 200px, share remaining space equally */ } .grid { grid-auto-rows: minmax(100px, auto); /* rows: at least 100px tall, grow to fit content */ } /* min-content and max-content as minmax arguments */ .grid { grid-template-columns: minmax(min-content, 200px) 1fr; /* first column shrinks to the smallest content unit but never exceeds 200px */ }

repeat()

/* repeat(count, track-definition) */ .grid { grid-template-columns: repeat(3, 1fr); /* 3 equal columns */ grid-template-columns: repeat(4, 200px 1fr); /* 8 columns: 200px 1fr 200px 1fr… */ grid-template-columns: repeat(12, 1fr); /* classic 12-column grid */ } /* auto-fill and auto-fit — responsive without media queries */ .grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

auto-fit vs auto-fill — the critical difference

auto-fit
Creates as many tracks as fit. Collapses empty tracks to zero width. If you have 3 items and space for 5 columns, the 3 items grow to fill the full container width. Items stretch to use all available space.
repeat(auto-fit, minmax(200px, 1fr))
auto-fill
Creates as many tracks as fit. Keeps empty tracks at their minimum size. If you have 3 items and space for 5 columns, the 3 items stay at minimum size and the 2 empty columns reserve space at the right edge.
repeat(auto-fill, minmax(200px, 1fr))
Use auto-fit for most card grids. You almost always want items to grow and fill available space when there are fewer items than columns. Use auto-fill when you want to preserve the column positions for future items — like a calendar grid or a fixed-column dashboard that must stay aligned even when some cells are empty.

4. Named Template Areas

header
nav
main
aside
/* The CSS that produces the layout above */ .layout { display: grid; grid-template-columns: 140px 1fr 100px; grid-template-rows: 44px 1fr 44px; grid-template-areas: "header header header" "nav main aside" "footer footer footer"; gap: 8px; min-height: 100dvh; } .header { grid-area: header; } .nav { grid-area: nav; } .main { grid-area: main; } .aside { grid-area: aside; } .footer { grid-area: footer; } /* Responsive: stack on mobile by redefining the template areas */ @media (max-width: 600px) { .layout { grid-template-columns: 1fr; grid-template-areas: "header" "nav" "main" "aside" "footer"; } }
Named areas implicitly create named lines. Declaring grid-area: header also creates lines named header-start and header-end (both column and row). You can reference these in grid-column: header-start / header-end — useful for overlapping items onto named regions without repeating line numbers.

5. Item Placement Properties

/* Placing items by line number */ .item { grid-column-start: 2; grid-column-end: 4; /* spans from line 2 to line 4 = 2 column tracks */ grid-row-start: 1; grid-row-end: 3; /* spans 2 row tracks */ } /* Shorthand: grid-column: start / end */ .item { grid-column: 2 / 4; /* line 2 to line 4 */ grid-column: 1 / -1; /* line 1 to last line — full width */ grid-column: 2 / span 3; /* start at line 2, span 3 tracks */ grid-column: span 2; /* auto-placed but spans 2 columns */ } /* grid-area shorthand: row-start / col-start / row-end / col-end */ .item { grid-area: 1 / 2 / 3 / 4; /* row 1→3, col 2→4 */ } /* Individual item alignment override */ .item { justify-self: center; /* inline axis — overrides justify-items */ align-self: end; /* block axis — overrides align-items */ place-self: center; /* shorthand: both axes */ }

6. Explicit vs Implicit Grid

/* Explicit grid — you define the tracks */ .grid { grid-template-columns: repeat(3, 1fr); grid-template-rows: 200px 200px; /* Defines a 3×2 explicit grid */ } /* If a 7th item is placed, it creates an IMPLICIT row */ /* By default implicit rows are sized to auto (content height) */ .grid { grid-auto-rows: minmax(200px, auto); /* All implicit rows: minimum 200px, grow to content */ } /* grid-auto-flow: dense — backfills holes with smaller items */ .masonry-ish { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-flow: dense; /* Items that span 2 columns leave a gap; dense fills it with the next small item */ /* WARNING: reorders items visually — bad for keyboard/screen reader users */ }

7. Named Lines

/* Lines can be named in square brackets */ .grid { grid-template-columns: [full-start] 1fr [content-start] minmax(0, 800px) [content-end] 1fr [full-end]; } /* Items then reference lines by name */ .full-bleed { grid-column: full-start / full-end; } .content { grid-column: content-start / content-end; } /* Great for article layouts where most content is centred */ /* but hero images, code blocks, etc. break out to full width */

8. Subgrid

Subgrid allows a nested grid to inherit and participate in its parent's track definitions. Without subgrid, nested grids have completely independent track sizes — columns can't align across cards. Supported in all modern browsers since late 2023.

/* The card alignment problem subgrid solves */ .card-grid { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: auto auto auto 1fr; /* row 1: image · row 2: title · row 3: body · row 4: CTA */ } .card { display: grid; grid-row: span 4; /* each card spans all 4 row tracks */ grid-template-rows: subgrid; /* inherit parent's row track sizes */ } /* Now all card images, titles, and body text align horizontally */ /* across cards even when content lengths differ */ .card__image { grid-row: 1; } .card__title { grid-row: 2; } .card__body { grid-row: 3; } .card__cta { grid-row: 4; }

9. Production Patterns

Responsive card grid (no media queries)
display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px;
Full-page layout
display: grid; grid-template-rows: auto 1fr auto; min-height: 100dvh; /* header · main · footer */
Sidebar layout
display: grid; grid-template-columns: minmax(200px, 25%) 1fr; /* sidebar never below 200px */
Perfect centring
display: grid; place-items: center; /* single-line centering works in any grid cell */
Overlapping items
/* multiple items, same area */ .grid { display: grid; } .image { grid-area: 1/1/3/3; } .caption { grid-area: 1/1/3/3; z-index: 1; align-self: end; }
Article with breakout
grid-template-columns: [full-s] 1fr [content-s] min(65ch,100%) [content-e] 1fr [full-e]; .content { grid-column: content; } .full { grid-column: full; }
Dashboard grid
grid-template-columns: repeat(12, 1fr); grid-auto-rows: minmax(80px, auto); .widget-sm { grid-column: span 3; } .widget-md { grid-column: span 6; } .widget-lg { grid-column: span 12; }
Masonry-ish (dense)
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-auto-rows: 8px; grid-auto-flow: dense; /* items set grid-row: span N JS measures height → sets N */

10. Grid vs Flexbox — When to Use Which

/* Use FLEXBOX when: */ /* - One-dimensional: a row of buttons, a horizontal nav, a stack of cards */ /* - Items dictate their own size and you distribute remaining space */ /* - Content-out: "fit as many as will go, then wrap" */ /* - Examples: nav bar, button group, tag list, media object (image + text) */ /* Use GRID when: */ /* - Two-dimensional: rows AND columns matter simultaneously */ /* - Layout-in: you define the layout structure first, then place content into it */ /* - Items need to align across rows (equal-height columns, card grids) */ /* - You need named areas, overlapping, or breakout elements */ /* - Examples: page layout, dashboard, data table, photo gallery, card grid */ /* They compose freely — a grid item can be a flex container and vice versa */

Chapter Summary

ConceptKey point
fr unitFraction of remaining space after fixed tracks are placed. Better than % because it accounts for gap. 1fr 2fr = 33% / 67% of available space.
minmax()Sets a min and max for a track. minmax(200px, 1fr) = at least 200px, grow to fill. minmax(min-content, auto) = shrink to content, grow freely.
auto-fit vs auto-fillBoth fill as many tracks as fit. auto-fit collapses empty tracks so items grow. auto-fill preserves empty track space. Use auto-fit for most card grids.
grid-template-areasNames regions visually in CSS. Makes page layouts readable at a glance. Implicitly creates named lines (area-start / area-end). Responsive via media query redefinition.
grid-column: span NPlace an item without specifying start line — auto-placed but spans N tracks. Combine with auto-fit for responsive span layouts.
-1 line numbergrid-column: 1 / -1 spans the full explicit grid width. Negative numbers count from the end edge of the explicit grid.
Implicit gridTracks created automatically for items that overflow the explicit grid. Size them with grid-auto-rows / grid-auto-columns.
grid-auto-flow: denseBackfills gaps with smaller items — changes visual order. Avoid for content with meaningful reading order.
subgridChild grid inherits parent track definitions. Solves cross-card alignment for titles, bodies, and CTAs. Supported in all modern browsers.
place-items: centerThe single cleanest way to centre content inside a grid cell. Shorthand for align-items + justify-items.
Exercises
  1. Named area layout: Build a full-page layout with header, sidebar nav, main content, and footer using grid-template-areas. Add a single media query that stacks everything into one column on mobile by redefining the template areas string.
  2. auto-fit card grid: Create a card grid using repeat(auto-fit, minmax(240px, 1fr)). Add 3 cards, then 6, then 2. Observe how cards grow when there are fewer than would fill a row. Compare with auto-fill.
  3. Spanning items: Build a photo gallery grid where the first photo spans 2 columns and 2 rows, and every other photo occupies a single cell. Use grid-column: span 2; grid-row: span 2 on the featured photo.
  4. Article breakout: Create an article layout using named lines — [full-start] and [content-start / content-end] and [full-end]. Make body text constrained to the content column but hero images and pull-quotes break out to full width.
  5. Subgrid cards: Build a 3-column card grid where each card has an image, title, body text, and CTA button. Without subgrid, observe how mismatched content lengths misalign the CTAs. Then apply grid-template-rows: subgrid to the cards and fix the alignment.
Next: Chapter 7 — Deep Dive: Responsive Design. Media queries in depth, container queries, the viewport meta tag, fluid sizing with clamp() and viewport units, intrinsic layouts that need no breakpoints, and a modern responsive workflow.