Grid
CSS Grid lays out children in two dimensions at once. You define a set of rows and columns on the container, and items are placed into the resulting cells — either automatically or by explicit coordinates.
The mental shift from flexbox: with flexbox the content drives the layout; with grid the layout is defined up front and content is placed into it.
Defining the grid
Section titled “Defining the grid”.grid { display: grid; grid-template-columns: 200px 1fr 200px; grid-template-rows: auto 1fr auto; gap: 1rem;}That creates a 3×3 grid. Direct children fill the cells in order, left to right then top to bottom.
Terminology
Section titled “Terminology”- Track — a row or a column.
- Line — the boundaries between tracks. An
n-track axis hasn+1lines, numbered from 1. Negative numbers count from the end, so-1is always the last line. - Cell — the intersection of one row and one column.
- Area — a rectangle of one or more cells.
The fr unit
Section titled “The fr unit”fr is a fraction of the leftover space after fixed-size tracks, gaps, and content-sized tracks are accounted for.
grid-template-columns: 200px 1fr 2fr;/* 200px is taken first; the rest is split 1:2 */1fr 1fr 1fr gives three equal columns — but only if the content fits. 1fr is shorthand for minmax(auto, 1fr), and that auto minimum means a track will refuse to shrink below its content’s minimum size.
Other track sizes
Section titled “Other track sizes”grid-template-columns: auto /* size to content, but grows to absorb free space if no fr present */ min-content /* narrowest without overflow */ max-content /* widest, no wrapping */ fit-content(300px) /* like max-content, capped at 300px */ minmax(150px, 1fr) /* at least 150px, at most one fraction */ 15%;repeat()
Section titled “repeat()”grid-template-columns: repeat(12, 1fr); /* 12-column grid */grid-template-columns: repeat(3, 200px);grid-template-columns: 250px repeat(2, 1fr) 250px; /* mixed */grid-template-columns: repeat(2, 1fr 2fr); /* -> 1fr 2fr 1fr 2fr */auto-fill vs auto-fit
Section titled “auto-fill vs auto-fit”Both let the browser decide the track count from the available width.
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem;}auto-fillcreates as many tracks as fit, leaving empty tracks if there are not enough items. Columns keep their slot.auto-fitcreates the same tracks, then collapses the empty ones to zero, so the remaining items stretch to fill the row.
With 2 items in a container wide enough for 5 columns: auto-fill gives two 240px cards and three empty columns; auto-fit gives two cards that each take half the width.
Use auto-fit when you want items to always fill the row, auto-fill when you want a stable column rhythm.
Named lines
Section titled “Named lines”.page { display: grid; grid-template-columns: [full-start] 1fr [content-start] minmax(0, 65ch) [content-end] 1fr [full-end];}.page > * { grid-column: content; } /* uses content-start / content-end */.page > .hero { grid-column: full; } /* breaks out to full width */A line name of foo with -start/-end suffixes automatically defines an area called foo, which is why grid-column: content works.
grid-template-areas
Section titled “grid-template-areas”The most readable way to define a page skeleton:
.layout { display: grid; grid-template-areas: "header header" "sidebar main" "footer footer"; grid-template-columns: 240px 1fr; grid-template-rows: auto 1fr auto; min-height: 100dvh; gap: 1rem;}
.header { grid-area: header; }.sidebar { grid-area: sidebar; }.main { grid-area: main; }.footer { grid-area: footer; }Rules: every row string must have the same number of cells, an area must form a rectangle, and a . (period) marks an empty cell.
grid-template-areas: "header header header" "sidebar main ." "footer footer footer";Rearranging for mobile becomes a one-property change:
@media (max-width: 48rem) { .layout { grid-template-areas: "header" "main" "sidebar" "footer"; grid-template-columns: 1fr; }}Placing items
Section titled “Placing items”By line number
Section titled “By line number”.item { grid-column-start: 1; grid-column-end: 3; /* spans columns 1 and 2 — the end line is exclusive */ grid-row-start: 2; grid-row-end: 4;}Shorthands:
.item { grid-column: 1 / 3; grid-row: 2 / 4; }.item { grid-area: 2 / 1 / 4 / 3; } /* row-start / col-start / row-end / col-end */grid-area also takes a name, which is the common form:
.item { grid-area: main; }By span
Section titled “By span”.wide { grid-column: span 2; } /* 2 tracks, auto-placed */.wide { grid-column: 2 / span 3; } /* start at line 2, cover 3 tracks */.full { grid-column: 1 / -1; } /* first line to last line: full width */grid-column: 1 / -1 is the workhorse for “this row spans the entire grid”.
Alignment
Section titled “Alignment”Grid has two levels of alignment: the tracks inside the container, and the items inside their cells.
Items in their cells
Section titled “Items in their cells”.grid { justify-items: stretch; /* inline axis: start | end | center | stretch (default) */ align-items: stretch; /* block axis */ place-items: center; /* align-items justify-items */}
.item { justify-self: end; /* override for one item */ align-self: center; place-self: center end;}stretch is the default, which is why grid items fill their cell.
Tracks in the container
Section titled “Tracks in the container”Relevant only when the tracks are smaller than the container (fixed-size tracks, no fr):
.grid { justify-content: center; /* inline axis: start|end|center|space-between|around|evenly */ align-content: center; /* block axis */ place-content: center;}Centring anything, the shortest version:
.center { display: grid; place-items: center; min-height: 100dvh; }The implicit grid
Section titled “The implicit grid”Items that do not fit into the explicit tracks create new ones automatically.
.grid { display: grid; grid-template-columns: repeat(3, 1fr); /* explicit columns */ grid-auto-rows: minmax(120px, auto); /* implicit rows get this size */ gap: 1rem;}With 10 items and 3 explicit columns, grid creates 4 implicit rows sized by grid-auto-rows. Without grid-auto-rows, implicit tracks are auto (content-sized).
grid-auto-flow
Section titled “grid-auto-flow”.grid { grid-auto-flow: row; } /* default: fill rows, add new rows */.grid { grid-auto-flow: column; } /* fill columns, add new columns */.grid { grid-auto-flow: row dense; } /* backfill holes left by spanning items */dense packs items into earlier gaps. It changes the visual order relative to the DOM, so the same accessibility caveat as flexbox order applies: keyboard and screen-reader order still follow the DOM.
grid-auto-columns sizes implicit columns, and is what you use for a horizontal auto-flow strip:
.strip { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(220px, 1fr); gap: 1rem; overflow-x: auto;}Responsive grids without media queries
Section titled “Responsive grids without media queries”The single most useful grid pattern:
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 1.5rem;}The min(260px, 100%) guard matters: on a viewport narrower than 260px, plain minmax(260px, 1fr) forces a track wider than the container and the page scrolls sideways. min(260px, 100%) caps the minimum at the container width.
A “sidebar that becomes a stack” with no breakpoint:
.with-sidebar { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 2rem;}Below 2 × 20rem + gap, auto-fit drops to one column and the layout stacks.
A holy-grail app shell:
.app { display: grid; grid-template-rows: auto 1fr auto; grid-template-columns: minmax(0, 16rem) minmax(0, 1fr); grid-template-areas: "header header" "nav main" "footer footer"; min-height: 100dvh;}Overlapping items
Section titled “Overlapping items”Grid items can occupy the same cell, and paint order follows DOM order (or z-index). This makes overlays trivial without absolute positioning:
.hero { display: grid; grid-template-areas: "stack";}.hero > * { grid-area: stack; } /* everything in the same cell */.hero img { inline-size: 100%; block-size: 100%; object-fit: cover; }.hero .caption { place-self: end start; padding: 1rem; color: white; }Unlike absolute positioning, the image still contributes to the grid’s size, so the container grows with its content.
subgrid
Section titled “subgrid”A grid item that is itself a grid can inherit its parent’s tracks, so nested content aligns across siblings.
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }.card { display: grid; grid-row: span 3; grid-template-rows: subgrid; /* use the parent's rows so titles, bodies and footers line up */}Supported in Firefox 71+, Safari 16+, and Chrome 117+. It is the correct solution to “make the buttons in these cards line up regardless of title length”.
Flexbox vs Grid
Section titled “Flexbox vs Grid”| Flexbox | Grid | |
|---|---|---|
| Dimensions | one axis at a time | two axes together |
| Driven by | content size | the track definition |
| Item count | unknown / variable is fine | works either way |
| Wrapping | flex-wrap, lines are independent |
tracks stay aligned across rows |
| Best for | navbars, toolbars, button rows, centring, “push this to the end” | page skeletons, card galleries, forms, dashboards, anything that must align in both directions |
They compose. A grid cell is often a flex container, and a flex item is often a grid.
Rule of thumb: if you find yourself setting widths on flex items to make columns line up between rows, you want grid. If you find yourself defining a single row of unknown items, you want flex.
Key points
Section titled “Key points”1frisminmax(auto, 1fr); useminmax(0, 1fr)whenever content might overflow.auto-fillkeeps empty tracks;auto-fitcollapses them so items stretch.repeat(auto-fit, minmax(min(260px, 100%), 1fr))is a complete responsive grid with no media queries.- The end line is exclusive:
grid-column: 1 / 3covers two tracks.1 / -1spans the explicit grid. grid-template-areasgives readable layouts and makes responsive rearrangement one property.place-items: centeron a grid container centres everything, both axes, one line.- Items sharing a cell overlap in DOM order — a cleaner overlay pattern than absolute positioning.