Card grids are often written as a set of breakpoint column counts: one column on mobile, two at tablet, three at desktop. That works until the grid appears in a narrower content well, a CMS title gets longer, or a sidebar variant reuses the same component.
The resilient pattern lets the container decide how many cards fit.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: 1rem;
}
auto-fit creates as many tracks as can fit. minmax() gives each card a useful minimum and allows it to expand. min(100%, 18rem) prevents the minimum from being wider than the container.
Make cards content-safe
The grid is only half the pattern. The cards need to handle uneven content.
.card {
display: grid;
gap: 0.75rem;
align-content: start;
min-inline-size: 0;
}
.card__actions {
margin-block-start: auto;
}
This keeps the action row at the bottom when the card has enough height, but does not require every card to have the same body length.
Avoid equal-height theater
Equal-height cards can look tidy, but they are not always better. If the grid contains article previews, mismatched heights may be fine. If it contains pricing plans, alignment probably matters. Choose equal heights when comparison is the task, not just because a design mockup looked clean with identical text.
Test the hostile cases
Before shipping a grid, test a long title, no image, a broken image, a long category label, and a card with one extra line of body copy. If the grid survives those cases without a new breakpoint, the pattern is working.