Skip to content

Fluid Card Grid Pattern

Fixed-column grids (grid-template-columns: repeat(4, 1fr)) need manual @media/@container breakpoints to drop columns, and when the item count isn’t a multiple of the column count, the last row stretches remaining cards to a different width than the rest.

This pattern replaces fixed columns with repeat(auto-fit, minmax(...)): the grid self-adjusts to available space, and .bp-card’s own named container (container-name: card) keeps its internal fluid typography correct even nested inside the grid’s container.

This is not a registered DS component — there are no --card-grid-* tokens shipped in DS CSS. Copy the snippet below into your own project.

Use when you want a fixed maximum column count (here, 2) that scales proportionally to the grid wrapper’s own width, not the viewport.

cqi variant

First card

Shrinks to one column once the grid wrapper itself narrows below ~2x49cqi.

Second card

Same width as its sibling at every breakpoint — no manual media queries.

Third card

An odd count never stretches the last card wider than the others.
<style>
.demo-card-grid--cqi {
container: card-grid / inline-size;
}
.demo-card-grid--cqi .demo-card-grid__cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(49cqi, 1fr));
gap: var(--bp-space-4);
}
</style>
<div class="demo-card-grid--cqi">
<div class="demo-card-grid__cards">
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">First card</h3>
</div>
<div class="bp-card__body">Shrinks to one column once the grid wrapper itself narrows below ~2x49cqi.</div>
</div>
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Second card</h3>
</div>
<div class="bp-card__body">Same width as its sibling at every breakpoint — no manual media queries.</div>
</div>
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Third card</h3>
</div>
<div class="bp-card__body">An odd count never stretches the last card wider than the others.</div>
</div>
</div>
</div>

Use when the minimum comfortable card width is a constant design decision, independent of the container.

rem variant

Card A

Never narrower than 15.625rem (250px) regardless of how many columns fit.

Card B

Columns wrap to a new row once they'd drop below the minimum.

Card C

Resize this preview to see the column count change without any breakpoint.

Card D

All cards stay the same width, including in a non-multiple final row.

Card E

Five cards — an odd count on purpose, to show the last row doesn't stretch.
<style>
.demo-card-grid--rem .demo-card-grid__cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15.625rem, 1fr));
gap: var(--bp-space-4);
}
</style>
<div class="demo-card-grid--rem">
<div class="demo-card-grid__cards">
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Card A</h3>
</div>
<div class="bp-card__body">Never narrower than 15.625rem (250px) regardless of how many columns fit.</div>
</div>
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Card B</h3>
</div>
<div class="bp-card__body">Columns wrap to a new row once they'd drop below the minimum.</div>
</div>
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Card C</h3>
</div>
<div class="bp-card__body">Resize this preview to see the column count change without any breakpoint.</div>
</div>
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Card D</h3>
</div>
<div class="bp-card__body">All cards stay the same width, including in a non-multiple final row.</div>
</div>
<div class="bp-card">
<div class="bp-card__header">
<h3 class="bp-card__title">Card E</h3>
</div>
<div class="bp-card__body">Five cards — an odd count on purpose, to show the last row doesn't stretch.</div>
</div>
</div>
</div>

Skip auto-fit/minmax for asymmetric layouts (sidebar 1fr 3fr, 55fr 45fr) or structural 12-column grids where the ratio between specific columns matters, not just a per-cell minimum. There, repeat(N, 1fr) or explicit named tracks are correct — auto-fit has no concept of column identity or ratio between columns.

.bp-card declares container-name: card (see src/styles/components/card.css). This matters here because the grid wrapper in this pattern is itself an inline-size container (container: card-grid / inline-size). An anonymous @container query on .bp-card would resolve to whichever inline-size container is nearest — which could be the grid wrapper instead of the card — once nested two containers deep. Naming the card’s container makes @container card (...) unambiguous regardless of nesting.