---
title: "CSS: Grid"
slug: "css-grid"
category: "frontend"
tags: ["frontend", "css", "grid", "layout", "subgrid"]
status: "stable"
last_updated: 2026-05-14
summary: "When grid beats flex for 2D layouts, track sizing with fr and minmax, subgrid for nested alignment, and the pitfalls that bite first."
related:
  [
    "[[frontend/css]]",
    "[[frontend/css-flexbox]]",
    "[[frontend/css-container-queries]]",
    "[[frontend/tailwind]]",
    "[[frontend/accessibility]]",
  ]
---

## Overview

Grid is the layout primitive for two-dimensional work: rows and columns that line up. Use it for page shells, dashboards, card galleries, and form rows where labels and inputs align across multiple rows. The umbrella overview lives in [[frontend/css]]; this page is the depth for the cases where grid earns its keep.

## Use grid for 2D, flex for 1D

Pick by the dimension count, not by familiarity. A single row or column with items that grow and shrink is flex territory; see [[frontend/css-flexbox]]. Anything with both axes locked, or alignment that must survive across rows, is grid.

```css
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  min-block-size: 100dvh;
}
```

Nesting flex inside grid inside flex is normal. Mix freely; do not force one primitive to do both jobs.

## Size tracks with `fr`, `minmax()`, and `auto-fill`

Avoid pixel-perfect grids. Use intrinsic sizing so the layout absorbs content variance.

- `1fr` distributes free space after intrinsic sizes resolve.
- `minmax(min, max)` clamps a track so it never collapses or runs away.
- `repeat(auto-fill, minmax(16rem, 1fr))` makes a responsive card grid in one line.

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

Use `auto-fit` instead of `auto-fill` only when you want surviving items to stretch and fill the row. The two read the same and behave differently at the last row.

## Reach for subgrid when children must align with the parent

Subgrid lets a nested grid inherit the parent's tracks. Use it when card internals (title, body, footer) need to line up across cards regardless of content height.

```css
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto 1fr auto;
}
.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
}
```

Subgrid ships in every evergreen browser as of 2025. Use it instead of brittle row-height hacks or JavaScript that measures and rewrites styles.

## Name lines and areas; do not count columns

Grid lines numbered 1, 2, 3 are unreadable two months later. Name the lines, or name the areas.

```css
.page {
  display: grid;
  grid-template-columns: [sidebar] 240px [content-start] 1fr [content-end];
  grid-template-areas:
    "sidebar header"
    "sidebar main"
    "sidebar footer";
}
.header {
  grid-area: header;
}
.main {
  grid-area: main;
}
```

Named areas survive refactors. A new column inserted in the middle reorders by name, not by index, and the call sites do not change.

## Separate layout from content

The grid container owns the layout. Children own their content. Do not put `width`, `margin-left: auto`, or `flex` on grid items that already participate in tracks.

```css
/* Wrong: fighting the grid with content rules. */
.card {
  width: 320px;
  margin-left: auto;
}

/* Right: let the grid size the track. */
.cards {
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
}
```

This separation is what makes a grid auditable. The container shows the layout; the items show the content. Pair it with [[frontend/css-custom-properties]] tokens for spacing and the grid stays editable in one place.

## Use `gap`, not margins

Grid (and flex) have first-class gutters. Use `gap`, `row-gap`, and `column-gap`. Margins on children leak through nested layouts and break with `gap`-aware utilities. See [[frontend/tailwind]] for utility-class equivalents (`gap-4`).

## Common pitfalls

- **Items overflowing tracks.** A child with `min-width: auto` (the default) refuses to shrink below its content. Set `min-width: 0` on grid items that should compress.
- **Counting numeric lines.** Lines 1, 2, 3 break the moment you add a column. Name lines or areas.
- **Mixing fixed and `fr` tracks without `minmax()`.** A `200px 1fr` grid collapses the `1fr` track when content overflows. Use `minmax(0, 1fr)`.
- **Hiding tabbed elements with `display: none` for layout reasons.** Hidden grid items skip the tab order; see [[frontend/accessibility]] for the keyboard rule.
- **Reordering items with `order` or `grid-row-start`.** Visual order and DOM order desync; screen readers follow the DOM.

## Related

- [[frontend/css]]
- [[frontend/css-flexbox]]
- [[frontend/css-container-queries]]
- [[frontend/tailwind]]
- [[frontend/accessibility]]
- [[seo/page-speed]]
