CSS Grid: A Practical Guide to Web Layouts
Learn CSS Grid from tracks and placement to responsive page layouts, with runnable examples, accessibility guidance, and practical troubleshooting.
CSS Grid is a two-dimensional layout system: it arranges elements in coordinated rows and columns. Use it for a page shell, a card collection, or any component where the relationship between both axes matters. Start with display: grid, define tracks with grid-template-columns and grid-template-rows, and set spacing with gap.
This guide builds from a small runnable example to flexible and responsive tracks, item placement, named page regions, alignment, accessibility, and troubleshooting. The examples use plain HTML and CSS; save one in an .html file and open it in a modern browser.
1. Your first grid
A grid container lays out its direct children as grid items. This example creates three equal columns with consistent spacing:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>First CSS Grid</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 2rem; font: 1rem/1.5 system-ui, sans-serif; }
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.card { padding: 1.5rem; background: #e9efff; border-radius: .5rem; }
</style>
<main class="cards">
<article class="card">One</article>
<article class="card">Two</article>
<article class="card">Three</article>
<article class="card">Four</article>
</main>
</html>
display: grid turns the element into a grid container. Its direct children become items. repeat(3, 1fr) repeats a track definition three times; 1fr allocates a share of available space. The fourth item starts a new row through auto-placement. gap adds space between tracks, not around the outside of the grid.
2. Tracks, lines, and sizing
Tracks are the rows or columns between grid lines. Explicit tracks come from grid-template-columns and grid-template-rows. If items need more tracks than you declared, auto-placement can create implicit tracks. The default implicit rows are content-sized; set grid-auto-rows when you want a predictable size.
| Value | What it does | Example |
|---|---|---|
| Fixed length | Sets a track to a specific size. | 16rem |
fr |
Shares remaining space among flexible tracks after fixed and intrinsic sizing. | 2fr 1fr |
auto |
Sizes based on content and available space, subject to grid sizing rules. | auto 1fr |
minmax(min, max) |
Sets a minimum and maximum track size. | minmax(14rem, 1fr) |
repeat() |
Repeats tracks without writing each one out. | repeat(4, 1fr) |
For example, this layout keeps a sidebar at a useful minimum width and gives the main region the remaining space:
.shell {
display: grid;
grid-template-columns: minmax(14rem, 1fr) 3fr;
gap: 1.5rem;
}
.sidebar, .content { min-width: 0; }
The min-width: 0 on grid children is useful when long text, code, or wide media would otherwise force a track wider than the available space. Use overflow-wrap: anywhere for unbroken strings if they should wrap.
Responsive repeated tracks
auto-fit and auto-fill can create as many repeated tracks as fit. Combine them with minmax() to let cards wrap without a breakpoint:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
Each card has a preferred minimum near 16rem, but the min() guard lets a single track shrink to the container width on a narrower screen. auto-fit collapses empty repeated tracks so occupied tracks can expand; auto-fill retains the empty track slots. Choose based on whether unused columns should reserve space. This pattern is responsive by its sizing rules; it does not guarantee that every design decision will suit every viewport.
3. Place items precisely
Grid lines are numbered from the start edge. You can position an item by start and end lines, or specify a span. This example places a feature card across two columns:
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
.feature { grid-column: 1 / 3; }
.tall { grid-row: span 2; }
The shorthand grid-column: 1 / 3 means start at line 1 and end at line 3, covering two tracks. A span is handy when the final number of tracks varies. Line placement is precise, but a fixed line number can become invalid or awkward if the template changes; review placements at each layout breakpoint.
Named lines and areas
For page structures, named areas can make the relationship between regions easier to read:
.page {
display: grid;
grid-template-columns: 15rem minmax(0, 1fr);
grid-template-areas:
"header header"
"nav main"
"footer footer";
gap: 1rem;
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Area names form a visual map: each row in grid-template-areas describes a row of cells, and repeated names join cells into a larger area. Use a period for an intentionally empty cell. The strings must describe a valid rectangular arrangement for each named area.
4. Build a responsive page shell
This complete example uses named regions on wider screens and deliberately changes to a single-column flow on smaller screens:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive Grid Shell</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 1rem; font: 1rem/1.5 system-ui, sans-serif; }
.page {
max-width: 72rem;
margin-inline: auto;
display: grid;
grid-template-columns: 15rem minmax(0, 1fr);
grid-template-areas: "header header" "nav main" "footer footer";
gap: 1rem;
}
header, nav, main, footer { padding: 1rem; background: #eef1f6; border-radius: .5rem; }
header { grid-area: header; }
nav { grid-area: nav; }
main { grid-area: main; min-width: 0; }
footer { grid-area: footer; }
@media (max-width: 45rem) {
.page {
grid-template-columns: minmax(0, 1fr);
grid-template-areas: "header" "main" "nav" "footer";
}
}
</style>
<div class="page">
<header>Site header</header>
<nav aria-label="Primary">Navigation</nav>
<main><h1>Page title</h1><p>Main content goes here.</p></main>
<footer>Site footer</footer>
</div>
</html>
The breakpoint is a design choice: at this width the sidebar and content would no longer have comfortable space side by side. Change it to fit the content. Grid can also respond without a media query, as in the auto-fit card example. Use a media query when the composition itself should change, such as moving navigation below the main content.
5. Align tracks and items
Alignment properties operate at different levels. justify-items and align-items align items inside their grid areas. justify-content and align-content distribute the grid tracks inside the container when there is extra space. The place-items and place-content shorthands set the corresponding align and justify properties.
.grid {
display: grid;
place-items: center; /* align-items and justify-items */
place-content: center; /* align-content and justify-content */
min-height: 20rem;
}
.card { justify-self: end; align-self: center; }
A common surprise is that align-content does not center items within their cells; it distributes the tracks as a group. Use item alignment for that. The default stretch behavior can also make a short item fill its grid area; set align-items: start if content should keep its natural block size.
6. Auto-placement and implicit tracks
When you do not explicitly place every item, the auto-placement algorithm fills the grid in source order. grid-auto-flow: row is the default; column fills down columns first. Adding dense asks the algorithm to backfill earlier holes:
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-flow: row dense;
grid-auto-rows: 10rem;
gap: .75rem;
}
.wide { grid-column: span 2; }
.tall { grid-row: span 2; }
Dense packing can make the visual order differ from source order as later items fill gaps. That may confuse keyboard users and people reading through assistive technology, so use it only when that visual reordering remains understandable. For predictable reading and focus order, keep source order aligned with the intended sequence and avoid using placement to disguise a poor document structure.
7. Grid and Flexbox: choosing the right tool
| Choose Grid when | Choose Flexbox when |
|---|---|
| Rows and columns need to coordinate, such as cards aligned across both axes. | Items primarily form one row or one column. |
| You want named regions for a page shell. | Items should flow along one axis and distribute or align within it. |
| Placement depends on a shared two-dimensional structure. | The content size should drive wrapping along a single axis. |
These tools work well together. A page can use Grid for its main regions and Flexbox for a toolbar inside one region. Choose based on the relationship between items, rather than trying to make every layout use one method.
8. Accessibility and source order
- Write meaningful HTML in the order users should encounter it. CSS visual placement does not change the document’s reading order or keyboard tab sequence.
- After changing placement at a breakpoint, check keyboard navigation and reading order at both wide and narrow widths.
- Use semantic elements such as
<main>,<nav>, and headings for their meaning, not simply because they are convenient grid items. - Do not rely on visual position alone to communicate sequence, grouping, or relationships.
MDN’s guides cover [CSS Grid layout](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout), [Grid learning examples and support](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Grids), and the [`grid` shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/grid). The core Grid standard is broadly supported in modern browsers; check compatibility separately when using newer features such as subgrid.
9. Troubleshooting common Grid problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Items are all in one column. | No column tracks were defined, or the selector does not match the container. | Confirm display: grid applies to the parent and set grid-template-columns. |
| A track is wider than the viewport. | Min-content sizing from long text, code, or media is preventing shrinkage. | Use minmax(0, 1fr), set min-width: 0 on the item, and constrain media with max-width: 100%. |
| Items appear in unexpected rows. | Auto-placement is filling in source order, or an item spans more tracks than expected. | Inspect the item’s row and column spans; use explicit lines or areas where placement must be predictable. |
| The grid has extra blank space. | Fixed tracks, explicit row definitions, or align-content distribution leave unused space. |
Review the track sizes and alignment rules; use fr or content sizing where appropriate. |
| Rows have unexpected heights. | Content size or implicit row sizing is controlling row height. | Set grid-auto-rows for implicit rows, or use minmax() for bounded sizing. |
| Named areas do not apply. | An area name differs between the template and an item’s grid-area, or the template rows are invalid. |
Match names exactly and ensure each named region describes a rectangle in every template. |
| Visual order differs from keyboard order. | CSS placement or dense auto-flow changed visual positions only. | Reorder the HTML to the meaningful sequence and reduce visual reordering. |
| Media overflows its cell. | The intrinsic dimensions exceed the track’s available size. | Set max-width: 100%; for a main track, consider minmax(0, 1fr). |
In browser developer tools, inspect the grid container’s overlay to see explicit and implicit lines, track sizes, and item placement. Check computed styles to confirm the intended selector and media query are active.
10. Performance, reliability, and compatibility
For ordinary page and component layouts, Grid is a browser layout primitive; choose track rules for clarity and avoid needless complexity. Large or frequently changing DOM trees and expensive style changes can still require profiling in the target page. Prefer a small number of understandable templates over elaborate placement rules that are difficult to maintain.
Grid itself does not guarantee a responsive or accessible result: content length, zoom, viewport changes, and source order still matter. Test the narrow and wide compositions, long labels, translated text, and keyboard sequence. The core standard is established in modern browsers; verify newer features such as subgrid against the browsers your project supports.
11. Capture a layout for review
A screenshot can help compare a layout at a fixed viewport during review. You can capture the page yourself with browser developer tools or automation, then check each responsive state. Keep screenshots as review artifacts, not as a substitute for testing keyboard order, zoom, or real content.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. A GET request returns a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms plus newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status.
Here is a one-call WebP capture; replace the target URL and API key. See the ScreenshotNeo API docs for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout -o shot.webp
The same endpoint supports full-page and selector captures, dark mode, device presets and custom viewports, retina scale, PDF settings, custom CSS and JavaScript, click and wait rules, request blocking, headers, cookies, user agents, timezone and geolocation, transparency, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
Plans include 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000. Bot checks, blank pages, and failed loads are never billed, and an MCP server lets AI agents capture pages. Sign up for 1,000 free screenshots a month, with no card required.
12. Frequently asked questions
Can Grid replace every use of Flexbox?
No. Grid is well suited to two-dimensional relationships; Flexbox remains useful for a row or column whose items align along one main axis. Combining them is common.
Do I need media queries to make a grid responsive?
No. Flexible tracks such as repeat(auto-fit, minmax(...)) can adapt to available width. Use media queries when the layout’s structure or ordering should change.
What happens to items without explicit placement?
The auto-placement algorithm places them in source order, creating implicit tracks as needed. Their positions depend on the explicit template, prior placements, and spans.
Should I use visual reordering for responsive layouts?
Only when the visual order still makes sense alongside the unchanged document and keyboard order. For a meaningful sequence change, reconsider the HTML source order.


