A CSS Grid That Auto-Fills, Then Reflows at a Minimum Cell Width
While building out Drupal 10 core, we landed on a CSS Grid pattern that solves a common layout problem: how to let a grid auto-fill columns up to a user-defined maximum, but gracefully shed columns when space runs out—without touching a line of JavaScript or reacting to viewport width.
The core requirements we set:
- The grid's default state is a specified maximum column count.
- If a cell drops below a minimum pixel width, the grid recalculates and reduces the column count.
- Cells always expand to fill the container's full width, regardless of how many columns are active.
- The behavior is entirely CSS-driven and independent of viewport size.
Here's a quick visual of what happens when the grid's container is squeezed—notice how the columns drop from four to three to two, and the cells keep stretching:

Prefer to just grab the finished solution? Here's the full code:
--gap-count: calc(var(--grid-column-count) - 1);
--total-gap-width: calc(var(--gap-count) * var(--grid-layout-gap));
--grid-item--max-width: calc((100% - var(--total-gap-width)) / var(--grid-column-count));
grid-template-columns: repeat(auto-fill, minmax(max(var(--grid-item--min-width), var(--grid-item--max-width)), 1fr));
The Building Blocks
The trick relies on a combination of CSS Grid's repeat(), auto-fill, and minmax() functions, plus the CSS max() and calc() functions. Let's break down how each piece contributes.
auto-fill: The Column Generator
auto-fill does the heavy lifting—it tells the grid to pack each row with as many columns as will fit. The key difference between auto-fill and auto-fit is explained well in Sara Soueidan's article on the topic.
But auto-fill alone would happily cram in dozens of narrow columns. We need a way to cap the growth.
max(): The Column Cap
That's where max() comes in. We want each cell's width to stop growing at a percentage that represents the desired column count—say 25% for a four-column layout. But we also need to enforce a hard floor on cell width.
For a four-column grid with a 100px minimum cell width, the expression looks like:
max(25%, 100px)
That 25% isn't quite accurate though, because it ignores the grid's gaps. What we actually need is this:
max(calc(25% - <grid-gap-for-one-cell>), 100px)
Which we can compute directly in CSS with calc():
--gap-count: calc(var(--grid-column-count) - 1);
--total-gap-width: calc(var(--gap-count) * var(--grid-layout-gap));
--grid-item--max-width: calc((100% - var(--total-gap-width)) / var(--grid-column-count));
Now the cell's maximum width accounts for the specified column count and the gaps, while never falling below the 100px floor:
max(100px, var(--grid-item--max-width))
For more on max() and its relatives, Chris Coyier's piece on min(), max(), and clamp() is a solid primer.
minmax(): The Stretch Factor
One more critical piece was missing: without it, the grid doesn't reliably stretch to fill its parent's full width. minmax() solves this directly. Setting the minimum to the computed <grid-item-width> and letting the maximum go to a flexible value means the grid will distribute any leftover space evenly across all cells:
minmax(<grid-item-width>, 1fr)
Putting It Together
Combining those tools produces the working pattern you saw earlier. The entire logic—maximum columns, minimum cell width, and the automatic reflow—resolves declaratively in the stylesheet. No resize listeners, no media queries tied to specific breakpoints.
It was a fun puzzle, and it's rewarding to see how far CSS has come—use-cases like this used to demand JavaScript. Thanks to Andy Blum for suggesting auto-fill over auto-fit, and to the spec writers and implementors for making these capabilities standard.



