CSS Grid Cheatsheet - Tracks, Areas & Auto-Fit
There is only one line between Grid and Flexbox, but crossing it is expensive: control a single axis and use Flex; align rows and columns together and use Grid. Page skeletons, a fixed header plus sidebar and content, or a dashboard of panels, are inherently two-dimensional. Forcing those onto nested Flex containers means three levels of wrappers just to line up columns, and the stylesheet collapses into indirection quickly. Size tracks with fr and minmax rather than percentages: fr shares leftover space in proportion, and minmax(200px, 1fr) gives each column a floor while keeping it flexible, so auto-fit and auto-fill produce responsive behaviour with no media queries at all. Sections cover the container, spacing and alignment, placing and spanning items, explicit and implicit tracks, common patterns, and responsive techniques, including grid-template-areas, which trades a little flexibility for a layout you can read at a glance. Naming tracks and areas with grid-template-areas costs some flexibility and buys a layout a colleague can read without tracing selectors.
Typical Use Case
For engineers laying out whole pages rather than one component: dashboards, card walls, galleries and form grids. Grid lets the parent define tracks while children declare where they land, instead of nested floats or piles of calc. Four groups: container definitions with repeat and named areas, spacing and alignment through gap and the justify and align families, spanning cells with line numbers and span, and implicit tracks. The traps are auto-fill versus auto-fit and 1fr inside minmax.
Container Definition 8
display: griddisplay: inline-gridgrid-template-columns: 1fr 2fr 1frgrid-template-rows: 100px auto 100pxgrid-template-columns: repeat(3, 1fr)grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))grid-template-areas: "header header" "sidebar main" "footer footer"Gap & Alignment 10
gap: 16pxrow-gap: 16pxcolumn-gap: 16pxjustify-items: stretchjustify-items: centeralign-items: centerplace-items: centerjustify-content: centeralign-content: centerplace-content: centerCells & Spans 7
grid-area: headergrid-area: 1 / 1 / 3 / 3grid-column: 1 / 3grid-row: 1 / span 2grid-column: 1 / -1grid-column: span 2grid-row: span 3Explicit & Implicit Tracks 6
grid-auto-columns: 100pxgrid-auto-rows: 100pxgrid-auto-flow: rowgrid-auto-flow: columngrid-auto-flow: densegrid-auto-flow: row denseCommon Patterns 6
grid-template-columns: 1fr 1fr 1frgrid-template-columns: 200px 1frgrid-template-columns: repeat(12, 1fr)grid-template-columns: minmax(0, 1fr) minmax(0, 1fr)grid-template-areas: "header" "main" "footer"grid-column: 1 / -1Responsive Tips 5
repeat(auto-fit, minmax(200px, 1fr))repeat(auto-fill, minmax(150px, 1fr))repeat(auto-fit, minmax(min(100%, 250px), 1fr))@media (max-width: 768px) { grid-template-columns: 1fr }grid-auto-rows: minmax(150px, auto)Parameter matrix
| Parameter | Effect | Example |
|---|---|---|
display: grid | Makes the container a grid formatting context; direct children become grid items automatically. | display: grid |
display: inline-grid | The grid participates in the outer flow as an inline-block, shrinking to its content — good for small controls embedded in text. | display: inline-grid |
grid-template-columns: 1fr 2fr 1fr | Explicit column tracks; fr distributes the leftover space. The most common three-column line. | grid-template-columns: 1fr 2fr 1fr |
grid-template-rows: 100px auto 100px | Explicit row tracks; auto rows are sized by content. The classic blend of fixed and flexible rows. | grid-template-rows: 100px auto 100px |
grid-template-columns: repeat(3, 1fr) | Repeats a track definition N times, saving a long hand-written string of 1fr. | grid-template-columns: repeat(3, 1fr) |
repeat(auto-fill, minmax(200px, 1fr)) | Derives the column count from container width: empty tracks are still filled, so on a narrower container the last row aligns to the left. | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) |
repeat(auto-fit, minmax(200px, 1fr)) | Same as above but collapses empty tracks, so items stretch to fill when the container widens — the go-to for responsive card walls. | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) |
grid-template-areas | Names regions with a string matrix, paired with grid-area for placement; the classic header/sidebar/main three-part layout. | grid-template-areas: "header header" "sidebar main" "footer footer" |
gap / row-gap / column-gap | Track spacing, replacing the margin hack, and it does not add outer margins at the container edges. | gap: 16px |
justify-items / align-items | Controls how grid items align within their track: justify along the inline axis, align along the block axis. | align-items: center |
justify-content / align-content | Controls how the whole track set is distributed inside the container; only has effect when items are fewer than tracks. | justify-content: center |
place-items / place-content | Shorthand for align and justify, setting both axes at once. | place-items: center |
grid-area: 1 / 1 / 3 / 3 | Declares row and column spanning at once with start/end lines; the order is row-start / column-start / row-end / column-end. | grid-area: 1 / 1 / 3 / 3 |
grid-column: 1 / 3 | Explicit column spanning; writing 1 / -1 spans from the first column to the last — the common full-bleed pattern. | grid-column: 1 / -1 |
grid-row: 1 / span 2 | Starts at row 1 and spans 2 rows; the span form does not depend on the total row count, so it holds up better across redesigns. | grid-row: 1 / span 2 |
grid-column: span 2 | Declares only the span with no start, letting the auto-placement algorithm decide where it lands — key to adaptive tile layouts. | grid-column: span 2 |
grid-auto-rows: 100px | Sets the height of implicit rows; without it they are content-driven and card heights tend to vary. | grid-auto-rows: 100px |
grid-auto-columns: 100px | Sets the width of implicit columns, common in horizontally scrolling containers. | grid-auto-columns: 100px |
grid-auto-flow: row / column | Decides the auto-placement direction: row fills row by row, column fills column by column. | grid-auto-flow: column |
grid-auto-flow: dense | Backfills gaps: small items slot into earlier gaps, at the cost of visual order detaching from DOM order. | grid-auto-flow: dense |
grid-template-columns: 200px 1fr | Fixed sidebar plus flexible main content, the most common line for admin pages. | grid-template-columns: 200px 1fr |
grid-template-columns: repeat(12, 1fr) | A 12-column grid; use grid-column: span N for proportional splitting, more reliable than hand-written percentages. | grid-template-columns: repeat(12, 1fr) |
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) | Pins the fr minimum to 0, equivalent to allowing shrink, so long content does not burst the track. | grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) |
repeat(auto-fit, minmax(min(100%, 250px), 1fr)) | min(100%, 250px) keeps a single column on narrow screens from being pushed into horizontal scroll by the 250px minimum. | grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)) |
@media (max-width: 768px) { grid-template-columns: 1fr } | Collapses multiple columns into one at the breakpoint — the easiest landing spot for responsive Grid. | @media (max-width: 768px) { grid-template-columns: 1fr } |
grid-auto-rows: minmax(150px, auto) | Gives implicit rows a minimum height while allowing content to grow; covers both needs for a tile wall. | grid-auto-rows: minmax(150px, auto) |
Common pitfalls
Symptomauto-fill and auto-fit look the same, but the last row of items does not fill the full row.
Causeauto-fill keeps empty tracks (1fr still gets space), while only auto-fit collapses empty tracks and gives the space to existing ones.
FixUse auto-fit when you want full-width fill; use auto-fill when you need alignment preserved (background stripes, numbered columns).
SymptomA horizontal scrollbar appears on narrow screens and content is cut off halfway.
CauseThe 200px in minmax(200px, 1fr) is a hard lower bound, so tracks stay 200px even when the viewport is narrower.
FixWrite the lower bound as min(100%, 200px), or set grid-template-columns to 1fr at the breakpoint.
SymptomOne column is stretched very wide by a long URL or code block and the others are squeezed.
CauseThe lower bound of fr is actually auto, i.e. the content minimum size; long words or nowrap push the track open.
FixAdd overflow-wrap: anywhere to the content, or write the track as minmax(0, 1fr) to allow shrinking at the root.
SymptomA child sets grid-column: 1 / 3 but does not span where expected — the element lands on the next row.
CauseThe four values of grid-area and grid-column/grid-row are row-start / column-start / row-end / column-end, the opposite of the intuitive order.
FixVerify row/column order value by value, or declare grid-column and grid-row separately, which is harder to get wrong.
SymptomItems have uneven heights and card bottoms do not line up.
CauseImplicit row heights are content-driven and inconsistent; only align-items was set and the row tracks were not handled.
FixGive implicit rows a common lower bound with grid-auto-rows: minmax(150px, auto), or switch to an explicit row template.
SymptomAfter adding dense, tab order / reading order no longer matches visual order.
Causedense backfills later items into earlier gaps, detaching DOM order from visual position.
FixDo not use dense when focus order and screen-reader order matter; instead use explicit grid-column to place each tile.
SymptomA grid-template-areas layout suddenly collapses into a single column somewhere.
CauseSpaces and newlines in the areas strings must align strictly to rows and columns; a row with fewer tokens than the others makes that row invalid.
FixKeep the token count per areas row equal and each named region rectangular; use minmax(0, 1fr) as a fallback when needed.
SymptomWith nested grids the inner gap stacks on the outer one, doubling the actual spacing.
Causegap is not an inheritable margin; when both parent and child set gap, each takes effect.
FixDecide clearly which layer owns the spacing (usually the outer one) and let the inner one set only padding.
Troubleshooting
1Confirm the container really is a grid formatting context.
grep -n "display: grid" -r srcPutting display on a child item instead of the container has no effect.
2Cross-check the areas definition against the grid-area names.
grep -n "grid-template-areas\|grid-area" -r srcWhen names are misspelled, the region is treated as an ordinary unnamed grid item.
3Find the element introducing horizontal scroll.
grep -n "minmax(" -r srcA minmax lower bound larger than the viewport width is the most common source of overflow.
4Check whether dense broke the focus order.
grep -n "dense" -r srcWhere dense is present, confirm DOM order matches visual order.
5Review column-collapse rules at the breakpoint uniformly.
grep -n "max-width: 768px" -r srcConfirm each multi-column container has a matching single-column override.
6Verify every implicit row has a height set.
grep -n "grid-auto-rows" -r srcWithout it row heights are content-driven, the root of uneven card-wall heights.
Command Examples
Named areas for a whole-page layout
.layout {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 200px 1fr;
gap: 16px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }Named areas separate layout from placement: the parent declares the structure once and children only announce a name, so visual order survives HTML reordering. That pays off in responsive work: collapse the template to one column on small screens and give each area a single grid-area, with no child changes at all.
auto-fit cards with no media query
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}auto-fit drops columns as the container narrows and collapses empty tracks, distributing the leftover space to the remaining ones, so cards always fill the width; auto-fill keeps empty tracks reserved, leaving a gap at the end of a row. Use auto-fill when the last row should not stretch.
minmax(0, 1fr) stops content from bursting the track
.grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}A 1fr track has an automatic minimum, meaning it must still fit its content; a long URL or nowrap string blows the track past the container and breaks the layout. minmax(0, 1fr) explicitly allows shrinking to zero and lets overflow handling inside do the work — the standard fix for overflow in a two-column grid.
Line-based placement for spanning items
.card {
grid-column: 1 / -1; /* span the full row */
}
.wide {
grid-column: 1 / 3; /* span 2 columns */
}
.tall {
grid-row: 1 / span 3; /* start at row 1 and span 3 rows */
}Lines are 1-based and -1 means the last line. grid-column: 1 / -1 is the sturdier way to say "span the full row": unlike span N it survives a template change from three columns to two, whereas span 3 would extend past the grid.
Implicit tracks and dense packing
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(150px, auto);
grid-auto-flow: row dense;
}When children outnumber the explicit columns, implicit tracks appear with a height fixed by grid-auto-rows, which clips content; minmax(150px, auto) gives a floor while letting content grow. row dense backfills later items into earlier holes for a tidier look, but visual order then differs from DOM order, which matters for keyboard and screen-reader navigation.
Common Pitfalls
- A fr track has an automatic minimum: a long URL, non-wrapping text, or a fixed-width image pushes the track past the container and produces a horizontal scrollbar. Use minmax(0, 1fr) instead of 1fr in any layout of two columns or more.
- The strings in grid-template-areas must form rectangles with an identical column count per row. Missing one cell makes the browser drop the whole rule and the layout silently collapses to normal block flow, which looks like "I added areas and nothing happened".
- align-items / justify-items position items inside their cells, while justify-content / align-content position the whole grid inside the container; the two pairs are unrelated. If the grid will not centre and you keep writing justify-items: center, that is the wrong property.
- After display: grid, child margins no longer inflate track sizing because tracks are computed from the content box, so layering large margins produces phantom gaps. Express spacing with gap and stop stretching layouts with margins.
- Browsers without grid support collapse the page to single-column flow rather than erroring. If IE or old Android WebView still matter, wrap the rules in @supports (display: grid) and provide a flex fallback.
- grid-auto-flow: dense breaks the correspondence between visual and DOM order, so Tab focus jumps around and screen readers announce a different sequence than the eye sees. Reserve it for purely decorative galleries with no focusable interactive children.
Tips
- fr is a flexible unit — 1fr 2fr means the second column is twice as wide as the first.
- minmax(200px, 1fr) means min 200px, max flexible fill; pair with auto-fill for responsiveness.
- grid-template-areas defines layout visually, improving code readability.
- gap replaces the old grid-gap and is the current standard syntax.
- auto-fit collapses empty tracks to stretch and fill; auto-fill keeps them — responsive layouts usually use auto-fit.
FAQ
What is the actual difference between auto-fill and auto-fit, and which one for responsive columns?
Both generate responsive columns via repeat(auto-xxx, minmax(200px, 1fr)); the difference shows up when the container has spare width. auto-fill keeps the empty tracks, so when you have fewer cards than columns the widths stay put and the row looks padded on both sides. auto-fit collapses the empty tracks and hands their space to the remaining ones, filling the row. Use auto-fit for fluid card grids whose item count varies, and auto-fill when a dashboard list needs a fixed-width column skeleton.
Content keeps blowing out my 1fr tracks — how do I fix it?
A 1fr track has an automatic minimum size, so a long unbreakable child can stretch it beyond the intended ratio. Writing minmax(0, 1fr) minmax(0, 1fr) forces the minimum to zero so the tracks divide space exactly as specified, and you handle the long content with wrapping or overflow instead. The same applies to grid-template-columns: 200px 1fr — swap that 1fr for minmax(0, 1fr) whenever the main column may contain wide content.
Should I use grid-template-areas or manual grid-column spans?
It depends on whether the layout shape is fixed. For stable page skeletons use grid-template-areas: "header header" "sidebar main" "footer footer" and let each child declare grid-area: header — the stylesheet then reads like a diagram and stays easy to maintain. For content whose position changes constantly and needs irregular placement, address the lines directly: grid-area: 1 / 1 / 3 / 3 is row-start / column-start / row-end / column-end, grid-column: 1 / -1 spans the full row, and grid-row: 1 / span 2 means two rows starting at line 1.
How do justify-items and justify-content differ, and which one do I edit?
They act at different levels. justify-items aligns the child inside each grid cell; justify-content aligns the whole set of tracks inside the container. If the content of a cell is not centered, change justify-items: center or place-items: center. If the tracks themselves are not centered in the container, change justify-content: center or place-content: center. The mental order is: align the tracks (content) first, then align what sits inside each cell (items).
Auto columns get squeezed into a sliver on small screens — how do I stop the horizontal overflow?
Use two levers together. First, wrap the minimum in min() as a floor: repeat(auto-fit, minmax(min(100%, 250px), 1fr)), so the track can never exceed the container width no matter how narrow the viewport gets. Second, at narrow breakpoints collapse to a single column with @media (max-width: 768px) { grid-template-columns: 1fr }, and reorder your grid-template-areas at the same time if needed. For row sizing, grid-auto-rows: minmax(150px, auto) lets rows grow with their content.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us