CSS Grid Layout Examples — From Basic to Advanced
Quick answer
💡Define a grid container with display: grid, then set column sizes with grid-template-columns. Use repeat(3, 1fr) for three equal columns, or repeat(auto-fit, minmax(200px, 1fr)) for a responsive grid that adjusts column count automatically. The fr unit divides remaining space after fixed sizes are subtracted.
Error symptoms
- ✕
Items stack in a single column even after adding grid-template-columns - ✕
Columns are equal in CSS but unequal on screen due to content differences - ✕
auto-fit and auto-fill behave the same and empty columns persist - ✕
Named grid areas are not placing children where expected - ✕
A full-width item is not spanning all columns - ✕
Gap property adds spacing inside the container edge instead of only between tracks
Common causes
- •display: grid missing on the parent — children remain in normal block flow
- •grid-template-columns written on a child instead of the grid container
- •Using auto-fill when auto-fit is needed — empty tracks remain and take space
- •grid-area name on child does not match grid-template-areas string exactly
- •grid-column: 1 / -1 used outside a grid container — line -1 is undefined
- •gap (formerly grid-gap) adding space between items but not understood as not padding
When it happens
- •Building page layouts with a fixed sidebar and flexible main area
- •Creating card grids that should reflow from 3 columns to 1 column on mobile
- •Building admin dashboards with named header, sidebar, main, and footer areas
- •Spanning a hero or banner element across all columns without wrapping
Examples and fixes
auto-fit with minmax creates a grid that adjusts column count based on container width.
Responsive card grid without media queries
❌ Wrong
<div class="article-grid">
<article class="post-card">Post 1</article>
<article class="post-card">Post 2</article>
<article class="post-card">Post 3</article>
</div>
/* CSS */
.article-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}✅ Fixed
<div class="article-grid">
<article class="post-card">Post 1</article>
<article class="post-card">Post 2</article>
<article class="post-card">Post 3</article>
</div>
/* CSS */
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.5rem;
}repeat(3, 1fr) creates exactly three columns at all screen widths. On a 375px mobile screen, three columns make each post-card about 112px wide — too narrow for readable content. Switching to repeat(auto-fit, minmax(260px, 1fr)) tells the browser to fit as many columns as possible where each column is at least 260px. On a 375px screen that produces one column; on 800px it produces two; on 1280px it produces three or four. No media queries needed.
A two-column layout with a fixed-width sidebar and flexible main area.
Sidebar and main content layout
❌ Wrong
<div class="page-layout">
<aside class="sidebar">Sidebar</aside>
<main class="content">Main content</main>
</div>
/* CSS */
.page-layout {
display: flex;
}
.sidebar {
width: 260px;
}
.content {
flex: 1;
}✅ Fixed
<div class="page-layout">
<aside class="sidebar">Sidebar</aside>
<main class="content">Main content</main>
</div>
/* CSS */
.page-layout {
display: grid;
grid-template-columns: 260px 1fr;
gap: 2rem;
align-items: start;
}Both approaches produce a sidebar layout, but Grid is cleaner because grid-template-columns: 260px 1fr makes the intent explicit — a 260px fixed column and one flexible column that fills the rest. The Flexbox approach requires understanding that flex: 1 means flex-grow: 1 and the width property sets the flex-basis. Grid also handles alignment between columns more predictably: align-items: start prevents the sidebar from stretching to match the content height when that is not desired.
Use grid-template-areas to define a full-page layout with header, sidebar, main, and footer.
Named grid areas for page structure
❌ Wrong
<div class="page-structure">
<header>Header</header>
<nav>Sidebar</nav>
<main>Main</main>
<footer>Footer</footer>
</div>
/* CSS */
.page-structure {
display: grid;
grid-template-columns: 220px 1fr;
}
header { grid-column: 1 / 3; }
footer { grid-column: 1 / 3; }✅ Fixed
<div class="page-structure">
<header class="site-header">Header</header>
<nav class="site-nav">Sidebar</nav>
<main class="site-main">Main</main>
<footer class="site-footer">Footer</footer>
</div>
/* CSS */
.page-structure {
display: grid;
grid-template-columns: 220px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'header header'
'nav main'
'footer footer';
min-height: 100vh;
}
.site-header { grid-area: header; }
.site-nav { grid-area: nav; }
.site-main { grid-area: main; }
.site-footer { grid-area: footer; }The numeric line approach works but is fragile — changing the number of columns requires updating every grid-column value manually. Named grid areas make the layout intent readable in the CSS itself. The grid-template-areas string shows the visual structure directly: header spans both columns, nav and main sit side by side, footer spans both again. Adding grid-area to children links them to the named areas. Changing the layout later means updating only grid-template-areas and the column sizes.
How CSS Grid places items automatically
CSS Grid uses an auto-placement algorithm that flows items into the next available cell unless you explicitly position them. When you define grid-template-columns: repeat(3, 1fr), the browser creates a three-column structure and places each child element into consecutive cells — first across row 1, then across row 2, and so on. This implicit row creation is controlled by grid-auto-rows and the grid-auto-flow direction.
The fr unit is central to CSS Grid. It stands for fractional unit and represents a share of the remaining space after fixed-size tracks are subtracted. In grid-template-columns: 200px 1fr 2fr, the 200px column takes 200px; the remaining space is divided into three parts, with 1fr getting one part and 2fr getting two parts. This is more predictable than percentage widths because it accounts for gap spacing automatically.
auto-fit and auto-fill are two keywords used inside repeat() for responsive grids. Both create as many columns as fit in the container at the given minimum size. The difference appears when items do not fill all available columns: auto-fit collapses empty tracks to zero width so filled items can stretch; auto-fill keeps empty tracks at their computed width, leaving visible gaps. For most responsive card grids, auto-fit is the correct choice because empty space is absorbed rather than reserved.
grid-template-areas is a powerful feature that defines the layout as a visual grid of quoted strings. Each string represents a row; repeated names span multiple cells. A period (.) represents an empty cell. Children are placed in their named areas using the grid-area property. This approach makes layout rearrangement at different breakpoints a matter of changing the template string, not rewriting placement rules for every element.
Inspecting grid layouts in browser DevTools
Select any element on the page in Chrome DevTools. If it has display: grid, a green grid badge appears next to it in the Elements panel. Click the badge to toggle the grid overlay, which draws all grid lines, row numbers, column numbers, and area names directly on the page. This is the fastest way to verify that your column and row definitions are producing the intended track sizes.
In the Layout panel, enable the option to show row and column numbers. The overlay then labels each grid line with its number, including negative numbers from the end. This makes it easy to verify that grid-column: 1 / -1 spans the full width — you can count from the last line backward and confirm -1 refers to the line you expect.
For named grid areas, the overlay shows area names inside the grid cells when template areas are defined. If a child element's grid-area does not match any name in the template, the overlay shows it placed by auto-placement rather than in the named region — a clear visual indicator of a name mismatch.
In the Computed tab, check the resolved values of grid-template-columns and grid-template-rows. Auto-placement values may differ from the shorthand you wrote because the browser resolves repeat(), auto-fit, auto-fill, and minmax() into explicit pixel or percentage values. Comparing the computed values to your expectations identifies where the calculation diverged from intent.
Chrome's Grid inspector also renders the computed track sizes inline on the overlay — column widths and row heights appear as labels directly on the grid lines. This eliminates the need to switch to the Computed tab to verify pixel dimensions. When a column is narrower or wider than expected, the inline label immediately surfaces the computed value so you can trace whether a minmax, fr calculation, or content override caused the discrepancy.
Building common layout patterns correctly
For an equal-column layout, repeat(N, 1fr) is the canonical pattern. Three columns: repeat(3, 1fr). Four columns: repeat(4, 1fr). Each column receives an equal share of the container width minus gaps. This is simpler than percentage widths because gaps are automatically excluded from the fractional calculation.
For a responsive grid that needs no media queries, repeat(auto-fit, minmax(MIN, 1fr)) is the standard approach. Set MIN to the narrowest width an item should be before collapsing to fewer columns. 220px works for text-heavy cards; 300px suits image-heavy product cards. The grid adjusts column count automatically as the container resizes.
For a full-width element inside a multi-column grid, use grid-column: 1 / -1 on that specific child. The value 1 is the first column line and -1 is the last column line regardless of how many columns exist. This span requires the element to be inside a grid container — line -1 is undefined outside a grid context.
For a fixed sidebar with flexible content, use grid-template-columns: FIXED 1fr. The fixed value should be a pixel width or a max-content value. Using 1fr for the second column ensures the content area takes all remaining space. On small screens, add a media query that changes to grid-template-columns: 1fr to stack the layout vertically.
For a classic holy grail layout — header spanning all columns, two sidebars flanking a main area, and a footer spanning all columns — use grid-template-areas combined with grid-template-columns: sidebar-width 1fr sidebar-width and grid-template-rows: auto 1fr auto. Named areas make the layout self-documenting and allow the entire structure to be rearranged for different breakpoints by rewriting just the template strings. The auto-fit keyword for columns collapses empty tracks when items do not fill a row, which is critical for card grids where the last row may have fewer items than the column count — auto-fit ensures those remaining items stretch to fill available space rather than leaving empty column placeholders.
Browser support and responsive grid gotchas
CSS Grid is supported in all modern browsers including Chrome 57+, Firefox 52+, Safari 10.1+, and Edge 16+. Internet Explorer 11 supports an early version with the -ms- prefix and several limitations — it does not support the repeat() function, grid-gap (instead use -ms-grid-column-gap and -ms-grid-row-gap), or auto-placement. Most production projects dropped IE 11 support by 2023.
auto-fit with minmax can cause unexpected behavior on very wide screens. If the container is 2400px wide and minmax(260px, 1fr) is set, the browser fits nine columns. Each column may be too wide for the content. Add max-width: 1200px to the grid container to limit how wide the grid itself can grow, preventing columns from becoming excessively wide.
Subgrid (grid-template-columns: subgrid or grid-template-rows: subgrid) allows a grid item to inherit its parent's tracks for its children. This is useful for aligning nested content like card titles across grid rows. Subgrid is supported in Chrome 117+, Firefox 71+, and Safari 16+. It is not available in older browsers and requires a fallback using align-items: start on the parent grid.
gap creates space between grid tracks but not between tracks and the container edge. If you want space between the grid items and the container border, add padding to the grid container itself. Developers sometimes expect gap to behave like margin on the outermost items — it does not. Only padding or the container's own box model properties create space at the edges.
Responsive grids built with repeat(auto-fit, minmax(MIN, 1fr)) eliminate the need for most breakpoint-based column changes, but there is still a known edge case: on viewports narrower than MIN, the single-column layout should use 100% width, but the minmax minimum locks column width to MIN pixels. Fix this with minmax(min(MIN, 100%), 1fr) to cap the minimum at 100% of the container on narrow screens.
Grid patterns that look right but break
Placing grid-template-columns on a div that is not a grid container has no effect. The property is silently ignored when display: grid (or display: inline-grid) is absent. This happens when CSS is copied and pasted into a selector that matches the wrong element, or when a developer adds grid-template-columns expecting it to also set display: grid implicitly.
Mixing auto-fill with 1fr and no minmax causes unexpected results. repeat(auto-fill, 1fr) does not make a responsive grid — 1fr items would need to be 0px minimum to fit infinitely many columns. The browser treats this as a miscalculation and often falls back to a single column. Always use minmax with auto-fill or auto-fit: repeat(auto-fill, minmax(200px, 1fr)).
Using grid-column: span N when the grid does not have N columns can cause items to be placed in the implicit grid with only one column. If a card has grid-column: span 3 but the grid only defines two columns, the browser creates an implicit grid track for the third column, and the card may appear to break the layout by creating a lone three-column row.
Forgetting that grid-template-areas strings must be rectangular — each row must have the same number of cells, and named areas must form complete rectangles. An L-shaped named area is invalid and causes the browser to ignore the entire grid-template-areas declaration without an error message. Use the DevTools grid overlay to see whether named areas are rendering.
Using grid-template-areas with named lines is an advanced pattern that looks correct but can mislead. Named grid lines follow the convention area-name-start and area-name-end. Defining grid-template-areas automatically creates these named lines, but manually defining named lines in grid-template-columns does not create a named area — grid-area on children will not match a line-name-only definition. The two systems must be used together correctly or named area placement silently falls back to auto-placement.
CSS Grid patterns for production layouts
Use grid-template-areas for any layout with a named semantic structure — page layouts with header, sidebar, main, and footer should always use named areas. The visual representation in the CSS makes it easy to understand and change the layout at any breakpoint by rewriting the template string.
Always test with both few and many grid items. A two-item grid inside a three-column template will have an empty cell in the third column. Decide whether that is acceptable or whether auto-fit should collapse empty tracks. Test with content that is longer than expected — a card title that wraps to three lines should not break the row height for all cards in that row.
Use gap instead of margin on grid children. margin on grid items adds space inside the item's grid cell; gap adds space between grid tracks. Combining both creates double-spacing that is hard to manage. Use gap for consistent gutters and padding on the grid container or individual items for internal spacing.
For responsive grids, write mobile styles first and use min-width media queries to add columns as the viewport grows. Define a single-column grid at the base, then switch to a multi-column template at appropriate breakpoints. This approach aligns with how content reflows naturally and produces cleaner CSS than starting with a desktop layout and overriding it downward.
Prefer explicit grid-template-areas definitions over numeric line placement for layouts with more than two columns. Line numbers become fragile when columns are added or removed. Named areas stay valid as long as the names in the template match the grid-area values on children.
Quick fix checklist
- ✓Confirm display: grid is on the parent container, not a child element
- ✓Use repeat(auto-fit, minmax(MIN, 1fr)) for responsive grids without media queries
- ✓Enable the Grid overlay in DevTools to see track sizes and line numbers
- ✓Use grid-column: 1 / -1 for full-width items inside any column count
- ✓Match grid-area names on children exactly to grid-template-areas strings
- ✓Use gap for gutters between items, and padding on the container for edge spacing
- ✓Test with more items than expected to verify auto-placement behavior
- ✓Use auto-fit to collapse empty tracks; use auto-fill to preserve them
Related guides
Frequently asked questions
What is the difference between auto-fit and auto-fill in CSS Grid?
Both create as many columns as the container can hold at the specified minimum size. The difference appears when items do not fill every column: auto-fit collapses empty columns to zero width so existing items can stretch to fill the container. auto-fill keeps empty columns at their minimum width, leaving visible gaps. Use auto-fit for card grids; use auto-fill when you want empty placeholder columns preserved.
What does 1fr mean in CSS Grid?
fr is the fractional unit. It represents one fraction of the space remaining after fixed-size tracks and gaps are accounted for. In grid-template-columns: 200px 1fr 2fr, the 200px column takes 200px; the remaining width is split into three fractions — one for the second column and two for the third. 2fr is twice as wide as 1fr.
How do I make one grid item span all columns?
Add grid-column: 1 / -1 to the specific grid item. The value 1 is the first column line; -1 is the last column line regardless of how many columns exist. This makes the item span the full width of the grid. The element must be a direct child of the grid container for grid-column to have any effect.
When should I use Grid instead of Flexbox?
Use Grid for two-dimensional layouts where you need to control both rows and columns simultaneously — page structures, card grids, dashboards. Use Flexbox for one-dimensional layouts where items flow in a single direction — navigation bars, button groups, form field rows. Both can overlap, but Grid shines when rows and columns must align across multiple items.
How do grid-template-areas work?
grid-template-areas defines a visual map of your layout using quoted strings, one per row. Repeat a name across multiple cells to span that area across columns or rows. Assign children to areas using the grid-area property with the matching name. Names in the template and grid-area values on children must match exactly, including case.
Why is gap adding space at the container edges?
gap does not add space at the edges — it only creates space between grid tracks. If you see space between your grid items and the container border, it is coming from padding on the container or margin on the items. Add padding: 1rem to the grid container if you want equal space between items and the container edge.
How do I create a masonry layout with CSS Grid?
True masonry in CSS Grid requires the experimental masonry value for grid-template-rows, currently behind a flag in Firefox. A common approximation uses grid-auto-rows with very short rows (grid-auto-rows: 10px) and positions each item with grid-row-end: span N where N is proportional to the item height. For reliable masonry, consider a JavaScript library or CSS columns.
Does CSS Grid work in all browsers?
CSS Grid is supported in all modern browsers: Chrome 57+, Firefox 52+, Safari 10.1+, and Edge 16+. Internet Explorer 11 has partial support with the -ms- prefix and does not support features like repeat(), auto-placement, or grid areas. Global IE 11 usage fell below 0.3% in 2023, so most projects safely omit IE 11 grid support.
All tools run in your browser. Your data never leaves your device. Last updated: 2026-05-06.