CSS Grid
CSS Grid Layout is a two-dimensional layout system that handles both columns and rows simultaneously. It is the most powerful layout system in CSS.
Display Grid
Setting display: grid on a container creates a grid container. Its direct children become grid items that can be placed into rows and columns.
.container { display: grid; /* block-level grid container */ } .container-inline { display: inline-grid; /* inline-level grid container */ }
Grid Line: the dividing lines that make up the structure. Grid Track: the space between two adjacent lines (a column or row). Grid Cell: a single unit of the grid. Grid Area: a rectangular area spanning one or more cells.
Grid Template Columns
Defines the number and size of columns in your grid. You can use fixed units, fractions, percentages, or any combination.
.grid { display: grid; /* Fixed pixel columns */ grid-template-columns: 200px 200px 200px; /* Fractional units — equal columns */ grid-template-columns: 1fr 1fr 1fr; /* Mixed units */ grid-template-columns: 250px 1fr 2fr; /* Using repeat() */ grid-template-columns: repeat(3, 1fr); /* Percentage-based */ grid-template-columns: 25% 50% 25%; }
Fractional Columns: 1fr 2fr 1fr
Mixed: 200px 1fr 1fr
Grid Template Rows
Defines the size of rows in the grid. Works identically to grid-template-columns but along the vertical axis.
.grid { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 100px 200px 100px; /* 3 rows */ } /* Common pattern: header, flexible content, footer */ .page-grid { display: grid; grid-template-rows: auto 1fr auto; min-height: 100vh; }
The fr Unit Deep Dive
The fr (fraction) unit represents a fraction of the available space in the grid container. It distributes space after fixed-size tracks and gaps are calculated.
/* Container is 900px wide, gap is 10px */ .grid { display: grid; grid-template-columns: 200px 1fr 2fr; gap: 10px; /* Available space = 900 - 200 - (10 * 2) = 680px 1fr = 680 / 3 = ~227px 2fr = 680 * 2/3 = ~453px */ }
200px is fixed, remaining space split 1:2
fr distributes available space (after gaps and fixed tracks), while % is based on total container width. With gaps, percentages can overflow; fr never will. Always prefer fr in grid layouts.
repeat() Function
The repeat() function provides a shorthand for repeating track patterns. Saves typing and makes intent clearer.
.grid { /* Instead of: 1fr 1fr 1fr 1fr */ grid-template-columns: repeat(4, 1fr); /* Repeat a pattern */ grid-template-columns: repeat(3, 1fr 2fr); /* = 1fr 2fr 1fr 2fr 1fr 2fr */ /* Responsive: auto-fit fills available space */ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Responsive: auto-fill preserves empty tracks */ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
minmax()
The minmax() function sets a minimum and maximum size for a grid track. Essential for responsive grids.
.grid { display: grid; /* Column: at least 200px, at most 1fr */ grid-template-columns: minmax(200px, 1fr) 2fr; /* Rows: at least 100px, grow with content */ grid-template-rows: minmax(100px, auto); /* The responsive grid pattern */ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
min 150px
min 150px
min 150px
min 150px
Resize the browser to see cards reflow automatically.
auto-fit vs auto-fill
Both create as many tracks as possible, but they differ when there are fewer items than tracks.
/* auto-fit: collapses empty tracks, items stretch */ .grid-fit { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } /* auto-fill: keeps empty tracks, items don't stretch */ .grid-fill { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
auto-fit: items stretch to fill the row
auto-fill: empty tracks preserved, items stay at min size
Notice how auto-fit stretches the 2 items across the full width, while auto-fill leaves space for empty columns.
Use auto-fit for most cases — items stretch to fill the container. Use auto-fill when you want items to maintain their minimum size and leave empty space on the right.
Gap
Sets spacing between grid rows and columns. Same property as in flexbox.
.grid { gap: 1rem; /* equal row and column gaps */ row-gap: 2rem; /* gap between rows */ column-gap: 1rem; /* gap between columns */ gap: 2rem 1rem; /* row-gap column-gap */ }
Larger row gap (1.5rem) and smaller column gap (0.75rem).
Grid Template Areas
The most visual way to define a grid layout. Name areas with grid-template-areas and assign items with grid-area.
.layout { display: grid; grid-template-columns: 250px 1fr; grid-template-rows: auto 1fr auto; grid-template-areas: "header header" "sidebar content" "footer footer"; min-height: 100vh; gap: 1rem; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .content { grid-area: content; } .footer { grid-area: footer; }
Use a dot (.) in grid-template-areas to leave a cell empty: "header ." — the second column in that row will be blank.
Grid Placement (grid-column / grid-row / span)
Place items on specific grid lines or make them span multiple tracks using line numbers.
.item { /* By line numbers */ grid-column: 1 / 3; /* start at line 1, end at line 3 */ grid-row: 1 / 2; /* start at line 1, end at line 2 */ /* Using span */ grid-column: span 2; /* span 2 columns from default position */ grid-row: span 3; /* span 3 rows */ /* Start at line 2, span 2 columns */ grid-column: 2 / span 2; /* Full width: start to end */ grid-column: 1 / -1; /* -1 = last line */ } /* Shorthand: grid-area: row-start / col-start / row-end / col-end */ .item { grid-area: 1 / 1 / 3 / 4; /* rows 1-3, columns 1-4 */ }
Named Lines
Give meaningful names to grid lines for more readable placement.
.grid { display: grid; grid-template-columns: [full-start] 1fr [content-start] 3fr [content-end] 1fr [full-end]; } .hero { grid-column: full-start / full-end; /* spans everything */ } .article { grid-column: content-start / content-end; /* centered content */ }
Implicit Grid
When items are placed outside the explicit grid, the browser creates implicit tracks. Control their size with grid-auto-rows, grid-auto-columns, and grid-auto-flow.
.grid { display: grid; grid-template-columns: repeat(3, 1fr); /* Size of auto-created rows */ grid-auto-rows: 150px; grid-auto-rows: minmax(100px, auto); /* at least 100px, grows with content */ /* Control flow direction */ grid-auto-flow: row; /* default — fill rows first */ grid-auto-flow: column; /* fill columns first */ grid-auto-flow: dense; /* fill holes in the grid */ grid-auto-flow: row dense; /* row order, backfill gaps */ }
The dense packing algorithm backfills empty cells in the grid. Great for masonry-like layouts where you have items of varying sizes. Note that it may reorder items visually.
Alignment
Grid has 6 alignment properties — they control how items are placed within their grid cells and how tracks are distributed within the container.
/* Container properties — align ALL items */ .grid { /* Align items within their cell (inline/row axis) */ justify-items: start | end | center | stretch; /* Align items within their cell (block/column axis) */ align-items: start | end | center | stretch; /* Distribute tracks in container (inline axis) */ justify-content: start | end | center | space-between | space-around | space-evenly; /* Distribute tracks in container (block axis) */ align-content: start | end | center | space-between | space-around | space-evenly; /* Shorthand: place-items = align-items justify-items */ place-items: center; /* Shorthand: place-content = align-content justify-content */ place-content: center; } /* Item properties — override alignment for ONE item */ .item { justify-self: start | end | center | stretch; align-self: start | end | center | stretch; place-self: center; }
Each item is centered within its grid cell.
Real-World Layouts
Responsive Card Grid
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; }
Dashboard Layout
.dashboard { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: minmax(120px, auto); gap: 1rem; } .widget-large { grid-column: span 2; grid-row: span 2; } .widget-wide { grid-column: span 2; }
Magazine Layout
.magazine { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 200px; gap: 1rem; } .featured { grid-column: span 2; grid-row: span 2; }
Grid vs Flexbox
They are complementary, not competing. Use both together for the best layouts.
| Feature | Flexbox | Grid |
|---|---|---|
| Dimensions | One-dimensional (row OR column) | Two-dimensional (rows AND columns) |
| Approach | Content-first (items determine layout) | Layout-first (grid defines the structure) |
| Best for | Components (navbars, card internals) | Page layouts, card grids |
| Item placement | Sequential (source order) | Anywhere on the grid |
| Gap support | Yes | Yes |
| Overlap items | No (not natively) | Yes (items can occupy same cells) |
| Dynamic # of items | Handles naturally | Use auto-fit / auto-fill |
Use Grid for the overall page structure and macro layouts. Use Flexbox for component internals and one-dimensional alignment. They nest beautifully — a grid item can be a flex container, and vice versa.
Gotchas
Grid items default to align-items: stretch and justify-items: stretch, meaning they fill their entire cell. If your items look bigger than expected, this is why. Set explicit alignment to override.
You cannot use repeat(auto-fit, 1fr) — it must be repeat(auto-fit, minmax(200px, 1fr)). The browser needs a minimum size to calculate how many tracks to create.
Avoid using percentage values for gap in grid — browser support is inconsistent and calculations can be unpredictable. Use rem, em, or px instead.
grid-template-areas only supports rectangular shapes. L-shaped or T-shaped areas are not valid and will break the layout silently.
Just like flexbox, grid items have min-width: auto by default. Long text or content can overflow grid cells. Fix with min-width: 0 or overflow: hidden.
Pro Tips
A full page layout with header, footer, sidebar, and main content using grid-template-areas is the cleanest solution CSS has ever had for this classic problem.
body { display: grid; grid-template: "header header" auto "nav main" 1fr "footer footer" auto / 250px 1fr; min-height: 100vh; }
subgrid allows a grid item's children to align with the parent grid's tracks. Use grid-template-columns: subgrid or grid-template-rows: subgrid on a grid item that is also a grid container. This solves the card alignment problem beautifully.
.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } .card { display: grid; grid-row: span 3; /* card spans 3 parent rows */ grid-template-rows: subgrid; /* aligns to parent row tracks */ }
Both Chrome and Firefox have excellent Grid inspectors. In DevTools, click the grid badge next to any grid container to see an overlay of grid lines, areas, and track names. This is the fastest way to debug grid layouts.
Unlike flexbox, grid items can occupy the same cells and overlap. Combined with z-index, this enables creative layouts without absolute positioning.