DEV Community

Cover image for Your Grid Has Invisible Columns: auto-fill vs auto-fit
Parsa Jiravand
Parsa Jiravand

Posted on Originally published at bestpractic.org

Your Grid Has Invisible Columns: auto-fill vs auto-fit

You wire up a card grid with the one-liner everyone reaches for:

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

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.

The word everyone treats as decoration

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.

What happens to the leftover columns

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.

🎮 Try it yourself

▶️ Open the interactive playground →

Runs right in your browser — poke at it and watch the concept react live.

The fix is the word, not the layout

  .cards {
    display: grid;
-   grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+   grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 1rem;
  }
Enter fullscreen mode Exit fullscreen mode

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.

Why auto-fill still exists

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.

🧠 Test yourself

Think it clicked? Take the 8-question quiz →

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

One word, two behaviors

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:

Top comments (0)