CSS Grid Guide: Designing Responsive Two-Dimensional Layouts
title: "CSS Grid Guide: Designing Responsive Two-Dimensional Layouts"
slug: "css-grid"
description: "Learn how CSS Grid uses rows and columns, repeat() and minmax(), auto-fit versus auto-fill, explicit and implicit grids, and accessible source order in a practical example."
tags: "CSS Grid, CSS, responsive design, web accessibility, frontend"
CSS Grid: design rows and columns together
A card collection, dashboard, or article page often has a relationship in two directions: how many items sit across a row and how those rows align below it. A one-dimensional layout tool alone cannot express that relationship particularly clearly. CSS Grid is a two-dimensional layout system: it defines intersecting horizontal and vertical lines, creates row and column tracks between them, and places items against those lines or within areas. That makes it useful both for large page regions and for compact interface components. Once an element has display: grid, its direct children become grid items.
Grid is not a replacement for every use of Flexbox. Flexbox is often the clearer choice when alignment and distribution along one axis are the real problem. Choose Grid when the rows and columns need to be designed as a system, rather than as an incidental result of wrapping.
Defining columns with grid-template-columns
grid-template-columns and grid-template-rows define the tracks in the explicit grid. The fr unit divides the free space left in the grid container after fixed track sizes and gaps have been accounted for. repeat(3, 1fr) is therefore a concise way to state three equal flexible columns. repeat() repeats a track list, so repeat(4, 1fr 2fr) is also valid when a pattern, rather than one value, must repeat.
Here is a complete, runnable card layout. The browser creates as many 16rem-or-wider columns as fit; remaining space is shared by the existing columns. Save the HTML and CSS together and open it in a browser.
<section class="products" aria-label="Featured articles">
<article><h2>Grid basics</h2><p>Rows and columns work together.</p></article>
<article><h2>Responsive cards</h2><p>The number of columns can change.</p></article>
<article><h2>Accessible order</h2><p>Reading order stays meaningful.</p></article>
<article><h2>Explicit placement</h2><p>Use it where structure calls for it.</p></article>
</section>
* { box-sizing: border-box; }
.products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: 1rem;
max-width: 72rem;
margin-inline: auto;
padding: 1rem;
}
.products > article {
padding: 1rem;
border: 1px solid #cbd5e1;
border-radius: .75rem;
background: #fff;
}
minmax(min, max) supplies a range for a track. In this example, minmax(16rem, 1fr) prevents a card column from becoming narrower than 16rem, but lets it expand into available space. It is not a guarantee that every real piece of content will fit elegantly: long unbroken strings, large media, and an item’s intrinsic minimum size still deserve testing. minmax() is valid in grid-template-columns, grid-template-rows, grid-auto-columns, and grid-auto-rows.
auto-fit and auto-fill: the important difference
Both keywords calculate an automatically repeated track list based on available space. The difference arrives after grid items are placed. auto-fill keeps repeated empty tracks. auto-fit behaves like auto-fill initially, then collapses empty repeated tracks to zero width, allowing occupied tracks to use the free room. That is why auto-fit commonly feels right for a short row of cards: the present cards can grow rather than leave reserved empty columns.
Neither keyword is universally better. Use auto-fill when the empty-track rhythm itself is meaningful, or when reserved columns and their lines are useful to the design. Use auto-fit when unused slots should disappear and occupied cards should spread out. Compare both with the actual range of item counts and container widths; their behavior is easiest to misunderstand if tested only with a full grid.
Explicit and implicit grid tracks
The columns declared by grid-template-columns: repeat(3, 1fr) are explicit. If more cards arrive than those columns can hold, Grid creates rows for them without a separate declaration. Those generated tracks form the implicit grid. By default, implicit tracks are automatically sized, affected by their content and the free space in the container. When generated rows need a controlled minimum, define them deliberately:
.dashboard {
display: grid;
grid-template-columns: 15rem 1fr;
grid-template-rows: auto;
grid-auto-rows: minmax(8rem, auto);
gap: 1rem;
}
.dashboard__summary { grid-column: 1 / -1; }
The summary spans from the first column line to the last. Line-based placement is valuable for structural elements such as a heading, summary, or sidebar. It is less helpful to assign coordinates to every data-driven card: new content and narrow screens then turn ordinary maintenance into a placement problem. Let auto-placement handle the ordinary sequence, and reserve explicit lines, names, or areas for deliberate structure.
Accessibility: visual placement must not become reading order
Grid can change where an item appears, but the DOM source order normally remains the order used for sequential keyboard navigation and by assistive technology. The CSS Grid specification specifically cautions authoring tools against using grid placement or order to reorder a document when that would put its source order—which determines speech and navigation order—out of sync with the visual order. Put the mobile-first, meaningful reading sequence in HTML and use Grid to arrange it visually, not to conceal a different sequence.