Your Grid Has Invisible Columns: auto-fill vs auto-fit
A responsive card grid that works perfectly with twelve items breaks with two — not from a bug, but from one word in a repeat() you copy-pasted without reading.

You wire up a card grid with the one-liner everyone reaches for:
No media queries, no JavaScript, cards that reflow from four columns down to one as the viewport shrinks. You test it with a full page of results. It's flawless. You ship it.
Then someone searches for something specific, gets two results back, and the page looks broken: two narrow cards huddled in the top-left corner, a slab of dead white space filling the rest of the row like the layout gave up halfway through rendering. No console error. No failed request. The CSS is identical to the page that worked.
The bug isn't in your code. It's in a single word you copy-pasted without ever being told it matters.
auto-fill and auto-fit show up in roughly the same tutorials, the same Stack Overflow answers, and the same "responsive grid in one line" blog posts — usually with whichever one the author happened to type first, no explanation for why. They both go inside repeat(), both pair with minmax(), both produce "as many columns as fit, each at least this wide." If you've only ever built grids with enough items to fill every row, you will never see them behave differently, which is exactly how the assumption that they're interchangeable survives so long.
They're computing the same thing up front: given the container's width and a minimum track size, how many columns can physically fit? Say the container is 1000px, your minimum is 240px, and the gap is 16px — the browser does the math and decides 4 columns fit. Both keywords agree on that number. Where they split is what happens to a column that has no card to put in it.
Keep that math: 4 columns fit, but you're only rendering 2 cards. Two columns get cards. Two columns get nothing.
auto-fill keeps the empty columns. They still exist as real tracks in the grid, sized by the same minmax(240px, 1fr) as every other track. Since a 1fr track claims a share of the row's leftover space whether or not it holds a card, those two ghost columns quietly eat two-fourths of the available width — width your two visible cards will never see. Your cards sit at close to their 240px minimum, pinned left, with a gap on the right that isn't a margin or a bug. It's two columns you can't see, reserved for cards that don't exist.
auto-fit collapses them. Same 4-column calculation up front, but any track that ends up with nothing assigned to it shrinks to 0px and stops competing for space. With the ghost columns out of the running, the 1fr in minmax(240px, 1fr) has only the 2 real columns left to divide the leftover width between — so your 2 cards stretch to fill the row, evenly, edge to edge.
Same container. Same items. Same minmax(). One word decides whether the leftover space goes to cards you can see or columns you can't.
Runs right in your browser — poke at it and watch the concept react live.
That's the entire diff. No restructuring, no wrapper <div>, no justify-content trick layered on top to fake a stretch that should've happened on its own. For the overwhelming majority of "responsive card grid" layouts — search results, product listings, dashboards, anything where the item count varies and you want whatever's there to fill the row — auto-fit is the one you actually meant to reach for.
It's not a worse version of auto-fit that nobody needed — it's for the layouts where you don't want a small item count to stretch. Picture a row of short filter chips or tags: ["React", "CSS"] on one search and ["React", "CSS", "TypeScript", "Vite", "Testing", "A11y"] on the next. Stretch two chips to fill a 1000px row with auto-fit and they balloon into two enormous pills that look like a rendering error. auto-fill keeps every track at its natural minimum size regardless of how many are occupied, so two chips look exactly as compact as chips are supposed to look — the empty reserved columns just never get an item, and nobody notices they're there because nothing needed that space to begin with.
The rule of thumb: if stretching the last row to fill the container is a feature, use auto-fit. If stretching it would look wrong, auto-fill is the correct choice, not an accidental one.
Think it clicked? Take the 8-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
auto-fill and auto-fit agree on the hard part — figuring out how many columns your container can physically hold — and disagree only on what happens to the ones that come up empty. Miss that, and the divergence hides for months in whichever screen size or result count happens to fill every row, then shows up as a "layout bug" the day it doesn't.
Go check the card, tag, and filter grids in whatever you're building right now. Which word did you type, and did you mean it?
🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 💬 Discord — join the frontend best-practices community: discord.gg/d9KRhuAwQ
- 📸 Instagram — frontend best practices, daily: @bestpractice___
Keep reading
One post a day, in your inbox
Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.
0 comments
Sign in to join the discussion, like comments, and save articles for later.