Grid
Date: 2026-09-27
Grid defines the tracks first, then places items into them. So the layout decides the sizes, and items in different rows line up.
repeat(auto-fit, minmax(…))gives a responsive layout without a breakpoint, andminmax(0, 1fr)fixes the column that won’t shrink.
CSS Grid is a two-dimensional layout mode: the container defines rows and columns (tracks), and children are placed into the cells between the grid lines. Two-dimensional means a column’s width is shared by every row, which is the thing Flexbox can’t do.
The shape
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: auto 1fr auto;
line 1 line 2 line 3 line 4
│ 200px │ 1fr │ 1fr │
├────────┼───────────────┼───────────────┤ ← row line 1
│ header ─────────────────────────────── │ auto
├────────┼───────────────┼───────────────┤
│ nav │ main │ aside │ 1fr
├────────┼───────────────┼───────────────┤
│ footer ─────────────────────────────── │ auto
└────────┴───────────────┴───────────────┘
Items are placed by line number (grid-column: 1 / -1 spans all), by span (span 2), or by name through grid-template-areas:
.page {
display: grid;
grid-template-columns: 200px 1fr 1fr;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
}
.page > header { grid-area: header; }The areas string is a picture of the layout, which is why it’s the most readable form for page-level structure.
The units
fr— a share of the free space left after fixed and content-sized tracks are sizedminmax(a, b)— no smaller thana, no larger thanbauto— sized to content, and stretches to fill if there’s spacemin-content/max-content— narrowest without overflow / widest without wrapping
The breakpoint-free card grid
The load-bearing idiom — as many columns as fit, each at least 240px:
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
gap: 1rem;
}gap 16px. n columns fit while n × 240 + (n − 1) × 16 ≤ width
container 1000px 4 → 1008 ✗ 3 → 752 ✓ → 3 columns of 323px
container 600px 3 → 752 ✗ 2 → 496 ✓ → 2 columns of 292px
container 300px → 1 column of 300px
container 200px → 1 column of 200px ← min() stops a 240px overflow
auto-fit vs auto-fill only differ when there are fewer items than would fit: auto-fill keeps the empty tracks, so two cards stay 240px wide with a gap beside them; auto-fit collapses the empties, so two cards stretch to fill the row. Usually auto-fit is what you meant.
This one line replaces most card-grid media queries — Responsive Design.
The blowout trap
1fr is really minmax(auto, 1fr) — the minimum is the content’s minimum size. A long unbroken string, a pre block or a wide table in one column forces it wider than its share, and the grid overflows.
grid-template-columns: 1fr 1fr; /* blows out on long content */
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); /* ← columns stay equal */Same cause as flexbox’s min-width: auto trap, same family of fix.
Subgrid
A nested grid normally has its own tracks, so the titles in a row of cards can’t line up if one wraps to two lines. grid-template-rows: subgrid on the card makes it adopt its parent’s row tracks, so every card’s title, body and button align across the row [CHECK: subgrid support across current browsers if an older-browser audience matters].
Failure modes
- Column blowout, above
- Visual order ≠ source order. Placing items with lines or areas can reorder them visually, and focus order won’t follow — Keyboard Navigation
- Grid for everything. A row of buttons with varying label lengths is content-sized: flex. Forcing it into tracks produces awkward equal widths
- Implicit tracks. Items placed beyond the defined grid create rows sized by
grid-auto-rows(defaultauto) — the source of “where did this extra row come from”
Where it connects
- Flexbox — the one-dimensional counterpart; the table there covers choosing between them
- Container Queries — for when the component, not the grid, needs to change shape