Fluid Card Grid Pattern
Description
Section titled “Description”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.
cqi variant — fixed low column target
Section titled “cqi variant — fixed low column target”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
Second card
Third card
<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>rem variant — fixed minimum width
Section titled “rem variant — fixed minimum width”Use when the minimum comfortable card width is a constant design decision, independent of the container.
rem variant
Card A
Card B
Card C
Card D
Card E
<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>When not to use it
Section titled “When not to use it”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.
Internals
Section titled “Internals”.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.