Module: CSS
CSS·029·9 MIN READ

029: CSS Grid

TOPICS COVERED: CSS Grid

Learning outcomes

By the end, you can identify grid container, tracks, lines, cells, and areas; define explicit columns and rows; use gaps and fr; place items; create intrinsic responsive tracks with minmax() and auto-fit; and choose Grid rather than Flexbox when two-dimensional alignment matters.

Prerequisites and retrieval

Retrieve why wrapping Flexbox aligns each line independently. Identify a design where headings, images, and actions should align in both rows and columns. Grid controls rows and columns together while keeping semantic source order.

Terminology

  • Grid container: An element with display: grid establishing grid formatting for its contents. — Source: CSS Grid 2
  • Grid item: An in-flow direct child of a grid container. — Source: CSS Grid 2: Grid items
  • Track: Space between adjacent grid lines — a row or column. — Source: CSS Grid 2: Tracks
  • Grid line: Numbered boundary lines delimiting tracks; placeable via line numbers. — Source: CSS Grid 2: Grid lines
  • Cell: The intersection of one row track and one column track. — Source: CSS Grid 2: Cells
  • Area: A rectangular region spanning multiple cells, usable as a placement target. — Source: CSS Grid 2: Areas
  • Explicit grid: Tracks defined by grid-template-columns/rows declarations. — Source: CSS Grid 2
  • Implicit grid: Auto-created tracks holding items beyond the explicit grid. — Source: CSS Grid 2: Implicit tracks
  • fr: A flexible-length unit distributing leftover space proportionally among tracks. — Source: CSS Grid 2: fr unit
  • minmax(): Track-sizing function clamping between minimum and maximum bounds. — Source: CSS Grid 2: minmax()
  • Gap (gap): "The gutter between grid rows and columns (row-gap / column-gap)." — Source: CSS Grid Level 2: Gutters
  • Auto-placement: "The algorithm that places grid items automatically when not explicitly placed." — Source: MDN: Auto-placement in grid

Mental model: define tracks, then place content

Grid creates a coordinate system. Direct children occupy cells and can span lines. Unlike Flexbox, tracks run across the whole grid, so items in different rows share column geometry. Content still influences intrinsic minimums, and 1fr is not simply a percentage.

Use Grid for page regions, galleries, and repeated cards requiring two-axis alignment. Use Flexbox for navigation, toolbars, and distribution along one primary axis. Both can be nested, but normal flow remains the default inside an item unless changed.

Beginner example: explicit portfolio grid

html
<section class="projects" aria-labelledby="projects-title">
  <h2 id="projects-title">Selected work</h2>
  <article class="project-card">...</article>
  <article class="project-card">...</article>
  <article class="project-card">...</article>
</section>
css
.projects {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}

.projects > h2 {
  grid-column: 1 / -1;
  margin-block-end: 0;
}

.project-card {
  padding: 1rem;
  border: 1px solid rgb(203 213 225);
  background: white;
}

The heading spans from line 1 to the final line (-1). Two equal flexible tracks divide available space. minmax(0, 1fr) explicitly lets tracks shrink below their content-based automatic minimum; content must still wrap appropriately. At narrow widths, two columns may become cramped. Do not add the responsive breakpoint yet; first understand the explicit grid, then 031 can introduce a content-driven breakpoint.

Use DevTools to display line numbers. Change one card to grid-column: span 2 and predict its placement. Restore it unless featured-card spanning serves the design.

Intermediate example: intrinsic auto-fitting card grid

Grid can often respond without media queries:

css
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1.25rem;
}

Read inside out. min(100%, 18rem) prevents the minimum track from exceeding a narrow container. minmax(..., 1fr) allows each track to grow. auto-fit creates as many tracks as fit and collapses empty tracks, so existing cards stretch. This is intrinsic responsive layout: available space and a meaningful card minimum determine columns.

Each card can use normal flow or 028's column Flexbox internally:

css
.project-card {
  display: flex;
  flex-direction: column;
}
.project-card__link { margin-block-start: auto; align-self: flex-start; }

Grid handles relationships among cards; Flexbox handles vertical distribution within each card.

Optional advanced example: named page areas

html
<div class="portfolio-layout">
  <main class="portfolio-layout__main">...</main>
  <aside class="portfolio-layout__aside">...</aside>
</div>
css
.portfolio-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(14rem, 1fr);
  grid-template-areas: "main aside";
  gap: 2rem;
  align-items: start;
}
.portfolio-layout__main { grid-area: main; }
.portfolio-layout__aside { grid-area: aside; }

Names make region intent readable. A narrow-screen variation belongs in 031. Keep main before aside in HTML so source order is sensible without Grid. Named areas must form rectangles.

Mistakes, debugging, and DevTools

  • Applying grid declarations to items instead of the intended container.
  • Forgetting only direct children are grid items.
  • Using 1fr 1fr and overlooking min-content overflow; inspect and consider minmax(0, 1fr).
  • Hard-coding many line positions while content changes.
  • Using Grid to visually reorder content away from DOM order.
  • Creating empty HTML elements solely as spacers; use gap or tracks.
  • Assuming auto-placement fixes an illogical source sequence.
  • Using explicit fixed columns on narrow screens without a collapse strategy.

Enable the Grid overlay to see tracks, gaps, line numbers, and areas. Inspect implicit tracks if an item appears unexpectedly. Toggle auto-fit/auto-fill: auto-fill retains potential empty tracks; auto-fit collapses them. Test long words and images because intrinsic content can widen tracks.

Accessibility and performance

Grid placement changes visual position, not the DOM's reading or focus sequence. Write logical source order first and avoid assigning distant explicit positions to interactive items. Allow content to enlarge and reflow; never use fixed row heights for text. Preserve local scroll only for genuinely two-dimensional content.

Ordinary grids perform well. Extremely large explicit grids or frequent layout-changing animation can cost more. Use responsive image files; a beautiful grid does not justify downloading oversized thumbnails.

Deep dive: explicit and implicit grids

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto;
}

Those declarations define the explicit grid. If more items require additional rows, Grid creates implicit tracks.

Control them with:

css
.grid {
  grid-auto-rows: minmax(8rem, auto);
}

For column-oriented auto-placement:

css
.grid {
  grid-auto-flow: column;
}

Most layouts should keep natural row-wise auto-placement unless the content model clearly requires something else.

Deep dive: auto-fit versus auto-fill

Both can create responsive track lists:

css
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

auto-fill keeps empty potential tracks in the grid. auto-fit collapses empty tracks so existing items can expand. With enough items they often look identical, so test a row with fewer items than the maximum number of tracks.

html
<section class="products">
  <article class="product product--featured">...</article>
  <article class="product">...</article>
  <article class="product">...</article>
  <article class="product">...</article>
</section>
css
.products {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem;
}

.product--featured {
  grid-column: span 2;
}

The featured span is only safe when enough columns exist. A more resilient version applies the span inside a container or media condition where the grid actually has room.

css
@media (min-width: 48rem) {
  .product--featured {
    grid-column: span 2;
  }
}

Deep dive: placement with named lines and areas

Named areas:

css
.page {
  display: grid;
  grid-template:
    "header header" auto
    "main   aside" 1fr
    "footer footer" auto
    / minmax(0, 1fr) 18rem;
  gap: 1.5rem;
}

.page__header { grid-area: header; }
.page__main   { grid-area: main; }
.page__aside  { grid-area: aside; }
.page__footer { grid-area: footer; }

Named lines can be better when the design is track-centric:

css
.layout {
  display: grid;
  grid-template-columns:
    [full-start] minmax(1rem, 1fr)
    [content-start] minmax(0, 70rem)
    [content-end] minmax(1rem, 1fr)
    [full-end];
}

A child can then span full bleed while other content aligns to the central track.

Deep dive: alignment in Grid

Grid has two alignment dimensions:

css
.grid {
  justify-items: stretch; /* inline axis within each area */
  align-items: start;     /* block axis within each area */
  justify-content: center;/* whole grid inside container */
  align-content: start;   /* whole grid in block axis */
}

Individual items may use:

css
.item {
  justify-self: end;
  align-self: center;
}

Do not confuse item alignment with track distribution.

Worked example: dashboard using minmax() safely

css
.dashboard {
  display: grid;
  grid-template-columns:
    minmax(0, 2fr)
    minmax(16rem, 1fr);
  gap: 2rem;
}

.dashboard__main {
  min-inline-size: 0;
}

The 0 minimum prevents content-based track minimums from causing unexpected horizontal overflow.

Deep dive: dense auto-placement caution

css
.gallery {
  grid-auto-flow: dense;
}

dense may backfill earlier visual holes with later items. That can make visual order differ from source order. Use it for non-sequential decorative galleries only when the changed visual order does not harm reading, keyboard, or comprehension.

Preview: subgrid

Modern Grid can let a nested grid participate in the parent's track sizing:

css
.card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
}

Subgrid is valuable for repeated cards whose internal title/body/action rows should align across the parent grid. It deserves deeper treatment in the modern CSS lesson after the roadmap-completion section.

Tiered exercises

Checkpoint: read a grid declaration inside out

For repeat(auto-fit, minmax(min(100%, 18rem), 1fr)), first evaluate the minimum. It is 18rem when the container permits, but never exceeds 100% of a narrower container. Each track may then grow to a fraction of remaining grid space. Finally, auto-fit repeats as many tracks as fit and collapses empty tracks. Reading functions inside out makes this declaration understandable rather than magical.

Compare auto-fill in an empty or lightly populated wide grid. It retains space for potential empty tracks; auto-fit collapses those tracks so existing items stretch. Neither is universally correct. A card gallery commonly benefits from auto-fit, while a design that must preserve potential column slots may choose auto-fill.

Test intrinsic minimums by placing a long unbroken token in one card. A track declared 1fr has an automatic minimum that may honor min-content and force overflow. minmax(0, 1fr) lets the track shrink, but the content still needs overflow-wrap, local scrolling, or a semantic break opportunity. Grid constraints and content handling are separate decisions, just as in Flexbox.

Now compare Grid and Flexbox with six cards. A wrapping flex line distributes its own space independently, so the last line can have different item widths. Grid's column tracks are shared across rows, aligning cards vertically and horizontally. Choose based on that relationship, not on which syntax is newer.

Placement never repairs source order. Set one card to grid-column: 2 and another to an earlier empty cell, then Tab through their links. Visual order can differ from sequential focus. Remove those declarations and retain a meaningful DOM sequence. Spanning a featured card is safe when it does not produce an order mismatch and when narrow layouts still fit.

Finally, inspect implicit rows. Add more cards than anticipated; Grid should auto-place them without overlap. Avoid fixed row heights because descriptions vary and enlarged text needs block growth. Use align-items and internal card layout when presentation needs consistent alignment, not clipped tracks.

Named lines can clarify repeated structures without named areas:

css
.article-grid {
  display: grid;
  grid-template-columns: [content-start] minmax(0, 2fr) [content-end aside-start] minmax(14rem, 1fr) [aside-end];
  gap: 2rem;
}

This is optional and verbose for a small portfolio. Use names when they make placement intent easier for a team to read; line numbers are adequate for simple spanning.

Alignment has two levels. justify-items and align-items establish defaults inside grid areas; justify-content and align-content distribute the grid tracks when the grid is smaller than its container. If changing align-content appears to do nothing, there may be no extra block space to distribute. As with Flexbox, inspect geometry before adding fixed height.

Grid areas must be rectangular. A template string cannot define an L-shaped named area. Keep narrow source order logical, then introduce named-area columns only at a justified query. If two areas exchange visual position, verify keyboard order; usually the safer fix is preserving the same sequence everywhere.

End with a content mutation: remove one card and add five. Auto-placement and intrinsic tracks should still produce a coherent gallery. A grid that works only for exactly six cards encodes a demo, not a reusable layout system.

Foundation: Create a two-column equal-track project grid with a heading spanning all columns. Inspect line numbers.

Core: Replace fixed tracks with an intrinsic auto-fit grid whose cards prefer at least 18rem but cannot overflow a smaller container.

Stretch: Build a main/aside named-area layout for wide available space. Keep DOM order correct and explain how it should collapse later.

css
.projects { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
.projects > h2 { grid-column: 1 / -1; }
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1.25rem;
}
.portfolio-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(14rem, 1fr);
  grid-template-areas: "main aside";
  gap: 2rem;
  align-items: start;
}
.portfolio-layout__main { grid-area: main; }
.portfolio-layout__aside { grid-area: aside; }

Recap and exit questions

Grid defines shared rows and columns. Tracks, line placement, spanning, gaps, and intrinsic track functions express two-dimensional relationships while content and source order remain central.

  1. How does a grid item differ from a descendant?
  2. What does 1 / -1 mean?
  3. Why use minmax(0, 1fr)?
  4. How do auto-fit and minmax() create intrinsic responsiveness?
  5. Which part of a card gallery belongs to Grid and which may belong to Flexbox?

Official references