CSS grid layout
CSS Grid Layout is a two-dimensional CSS layout system that divides a container into rows and columns, letting you place items precisely across both axes at once.
Theory
TL;DR
- Think of it like a spreadsheet: define column and row tracks, then drop items into specific cells
- Main difference from Flexbox: Grid controls rows and columns simultaneously; Flexbox works one axis at a time
- Use Grid for page structure (header, sidebar, content, footer); use Flexbox inside grid cells for component alignment
frunit distributes free space after fixed andautotracks are sizedgrid-template-areasgives you a visual map of your layout directly in CSS
Quick example
.container {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* narrow | wide | narrow */
grid-template-rows: auto 1fr auto; /* content | flex | content */
gap: 1rem;
height: 100vh;
}Three columns (equal, double, equal), three rows (content-sized, flexible, content-sized). Items fill left to right, top to bottom by default. Add grid-template-areas and you get a named layout readable at a glance.
Grid vs Flexbox
Grid creates explicit tracks on the container and places items into those intersections. Flexbox lines items up along one axis and wraps them when needed. Neither replaces the other.
Grid is the page skeleton. Flexbox handles the muscles inside each cell.
The practical rule: if you need to control rows and columns together (dashboard, magazine layout, overlapping elements), use Grid. If you have a row of buttons or a list of cards that just needs to wrap, Flexbox is simpler.
When to use
- Full-page layout with header, sidebar, content, footer: Grid with
grid-template-areas - Dashboard with panels spanning multiple columns: Grid with
grid-column: 1 / -1 - Nav bar with links spread horizontally: Flexbox
- Card component internals (icon + text aligned): Flexbox
- Responsive gallery with unknown item count:
repeat(auto-fill, minmax(250px, 1fr)) - Two elements overlapping without absolute positioning: Grid, same cell area, different
z-index
Comparison table
| Feature | CSS Grid | Flexbox |
|---|---|---|
| Dimensions | 2D: rows and columns | 1D: main axis + cross axis |
| Item placement | Explicit lines, areas, or auto-flow | Flow order with optional wrapping |
| Spanning | Native grid-column: 1 / 3 | Not available natively |
| Named areas | Yes, grid-template-areas | No |
| Best for | Page layouts, dashboards, overlapping content | Nav bars, card internals, button groups |
Core properties
Container properties define the grid structure. grid-template-columns and grid-template-rows set track sizes. gap adds space between tracks only (no outer edges by spec). grid-template-areas maps named regions visually.
.layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar content aside"
"footer footer footer";
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
gap: 1rem;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }No floats. No clearfix. No position: absolute. That whole layout took six lines.
Item properties control placement and alignment. grid-column: 1 / 3 spans from line 1 to line 3. grid-column: 1 / -1 always spans the full row, regardless of column count. justify-self and align-self align a single item within its cell without touching the rest.
repeat() and minmax()
repeat(3, 1fr) is three equal columns. The more useful pattern is repeat(auto-fill, minmax(250px, 1fr)). That creates as many columns as fit, each at least 250px wide, distributing remaining space proportionally. Replace auto-fill with auto-fit and empty trailing tracks collapse, so items stretch to fill the row.
The difference matters when you have fewer items than possible columns. auto-fill keeps ghost tracks visible. auto-fit removes them.
I have seen fr columns collapse to near zero on small screens more times than I can count. The fix is always the same: use minmax(200px, 1fr) instead of plain 1fr. The fr unit only distributes free space; when there is none, tracks shrink to min-content width.
How the browser handles this
display: grid triggers a grid formatting context. The browser first resolves fixed track sizes, then auto, then distributes fr units from what remains. Explicitly placed items are positioned first; the rest auto-flow into open cells. Resize the viewport and the track sizing algorithm runs again. That is why heavy minmax and fr combinations on large grids can cost more on resize than a simple Flexbox flow.
Common mistakes
Setting justify-items: center to center one item.
justify-items centers every item in every cell. For a single item, use justify-self: center on that element directly.
/* Moves all items - probably not what you want */
.container { justify-items: center; }
/* Moves only this one */
.featured { justify-self: center; }Expecting gap to add outer spacing.
gap inserts space between tracks only. There is no trailing gap by spec. If you need space around the outside, add padding to the container.
Using plain 1fr on small screens.
fr only takes free space. When the container has no free space, tracks can shrink to min-content width. Set a floor with minmax(200px, 1fr) instead.
/* Fragile on small screens */
grid-template-columns: repeat(3, 1fr);
/* Safer */
grid-template-columns: repeat(3, minmax(200px, 1fr));Nested grids with no height.
A child with display: grid has no height unless the parent cell stretches it. align-items: stretch is the default on the parent, but overrides happen. Check that first when a nested grid collapses.
Overlapping items stacking in the wrong order.
Items overlap in DOM order by default. Add explicit z-index values to control which one sits on top. Grid items accept z-index the same way positioned elements do when they share overlapping areas.
Real-world usage
- Tailwind CSS:
grid grid-cols-12 gap-4for responsive dashboards - Bootstrap 5:
row g-3with column wrappers for card grids - Material-UI (MUI):
Grid container spacing={2}in React apps - Chakra UI:
SimpleGrid columns={4}for auto-flow galleries - GitHub repo page: file list uses grid for icon, filename, and action column alignment
Follow-up questions
Q: What is the difference between grid-template and grid-template-columns?
A: grid-template is a shorthand that combines grid-template-columns, grid-template-rows, and grid-template-areas in one declaration. Use individual properties when you need to override just one dimension without touching the others.
Q: How does the implicit grid work?
A: Items placed beyond the explicit grid create new tracks automatically. grid-auto-rows and grid-auto-columns control implicit track sizes. grid-auto-flow: dense fills gaps by visually reordering items, while DOM order stays unchanged.
Q: What is subgrid?
A: Subgrid (CSS Grid Level 2) lets a child grid align its tracks to the parent grid instead of creating independent ones. You set grid-template-rows: subgrid or grid-template-columns: subgrid on the child. Useful for aligning labels and values across multiple cards. Browser support in modern browsers is solid as of late 2024.
Q: When does Grid reflow cost more than Flexbox?
A: Complex minmax and fr calculations repeat on every resize. A large grid with many auto-sized tracks runs a heavier algorithm than a simple Flexbox row. Profile with Chrome DevTools Performance panel before optimizing.
Q: Can you build a masonry layout with CSS Grid alone?
A: grid-template-rows: masonry is in the spec and was available behind a flag in Firefox. As of 2024, no stable browser ships it. Current fallbacks are CSS column-count or JavaScript. Knowing the spec exists and where implementation stands signals senior-level awareness.
Examples
Basic responsive page layout
.page {
display: grid;
grid-template-areas:
"header"
"content"
"footer";
grid-template-rows: auto 1fr auto;
min-height: 100vh;
gap: 1rem;
}
@media (min-width: 768px) {
.page {
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 250px 1fr;
}
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }Mobile stacks everything vertically. Above 768px, sidebar appears on the left. The entire layout switch is two property changes inside a media query.
Dashboard with spanning panels
.dashboard {
display: grid;
grid-template-columns: 250px 1fr 300px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"sidebar header stats"
"sidebar content stats"
"sidebar footer footer";
gap: 2rem;
min-height: 100vh;
}
.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
.content { grid-area: content; }
.stats { grid-area: stats; }
.footer { grid-area: footer; }Sidebar spans all three rows on the left. Stats panel spans two rows on the right. Footer covers the last two columns. This is the structure behind most admin panels built with Next.js and Remix.
Overlapping items with z-index
.canvas {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 100px);
gap: 10px;
}
.card-red {
grid-column: 1 / 3; /* top-left 2x2 block */
grid-row: 1 / 3;
background: #e55;
z-index: 1;
}
.card-blue {
grid-column: 2 / 4; /* overlaps bottom-right corner of red */
grid-row: 2 / 4;
background: #55e;
z-index: 2;
}Blue overlaps the bottom-right corner of red without any clipping. No absolute positioning needed. z-index works on grid items the same way it works on positioned elements when they share overlapping areas.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.