Responsive Card Grids with CSS Grid
Build a card layout that adapts to any screen without a single breakpoint, and line up the content of every card with subgrid.

Quest objectives
- Create a responsive grid with auto-fit and minmax()
- Understand the difference between auto-fit and auto-fill
- Let a card span several columns
- Align the insides of all cards with subgrid
Card grids are everywhere: shops, blogs, level selection screens. They used to need a pile of media queries. With modern CSS Grid, a single line handles nearly everything.
The markup
A list is the right element here, since screen readers will announce how many cards there are:
<ul class="cards">
<li class="card">
<h2>Forest of Whispers</h2>
<p>A short description of this level.</p>
<a href="/levels/forest">Play</a>
</li>
<!-- More cards -->
</ul>One line for the whole layout
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
gap: 1.5rem;
padding: 0;
list-style: none;
}The line with grid-template-columns does all the work. Read it from the inside out:
min(16rem, 100%)is the smallest width a card may have. Themin()stops a card from overflowing on screens narrower than 16rem.minmax(..., 1fr)lets each column grow from that minimum to an equal share of the free space.repeat(auto-fit, ...)fits in as many columns as there is room for.
Resize your browser window and the columns wrap on their own. There isn't a single media query in sight, and the grid even adapts to the width of its container rather than the screen, so it works inside a sidebar too.
A featured card
To make a card stand out, let it span two columns. The media query is still useful here: on a narrow screen there is only one column, and spanning two would create an extra one and cause overflow.
@media (min-width: 40rem) {
.card--featured {
grid-column: span 2;
}
}Line up the cards with subgrid
Here's a problem you may have seen: one card has a long title, so its text and button sit lower than those of its neighbors. subgrid solves this. A card can take over the row tracks of the parent grid, which means that all titles, texts and buttons in a row share the same lines.
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
gap: 0.75rem;
padding: 1rem;
border-radius: 0.75rem;
background: #eef;
}
.card h2,
.card p {
margin: 0;
}grid-row: span 3 claims three rows of the parent, one each for the title, the text and the link. grid-template-rows: subgrid then hands those rows to the card's children. Every title row now grows with the tallest title in that row.