Page loaded: Miguel Carino | Angular Front-End Architect

Photo by Daniel Salcius on Unsplash

Web Platform

CSS Grid That Replaced Half My Media Queries

The card wall had four breakpoints, and none of them knew how much space they actually had. One line of grid asked the container instead of guessing at the viewport.

Miguel Carino
Focus
Front-end architecture, Angular, team leadership, and stakeholder communication
Experience
25+ years turning enterprise web complexity into maintainable products

The card grid had four breakpoints. One column on phones, two on tablets, three on laptops, four on wide screens — each one a media query, each one a hand-picked pixel value that matched no real device, just the widths where the previous layout started to look cramped. Every time a card's minimum comfortable width changed, all four numbers were wrong again, and I'd re-guess them.

The whole stack was answering the wrong question. A media query asks how wide is the viewport? But the thing I actually cared about was how many cards fit here, given how much room this grid has and how narrow a card is allowed to get? Those aren't the same question, and the gap between them is every awkward in-between width where three fat columns would have been better as four, or two cramped ones should have been one.

Media queries guess at the viewport; the layout knows its own space

A breakpoint is a guess encoded as a number. min-width: 768px is shorthand for "somewhere around here, a second column probably fits" — but "probably" is doing a lot of work. It doesn't account for the grid living in a narrower column on some pages, or a sidebar eating half the width, or the card content needing more room after a copy change. The viewport width and the grid's available width are only the same thing in the simplest layout, and the moment they diverge, your breakpoints are tuned to the wrong measurement.

That's why the four queries felt endless. They weren't describing the layout; they were describing a series of viewport widths and hoping the layout followed.

One intrinsic grid, and the breakpoints dissolve

CSS Grid can express the question I actually meant. repeat(auto-fit, minmax(min, 1fr)) says: fit as many columns as you can, each at least min wide, and let them share the leftover space equally. The browser does the arithmetic against the grid's own width, continuously, at every size — no breakpoints, because there's nothing to guess.

.card-wall {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}

That one declaration replaced all four media queries. At 1400px it lays out four or five columns; in a narrow sidebar it drops to one; at every width in between it packs in exactly as many 16rem-or-wider cards as fit and stretches them to fill the row. The layout reflows because the space changed, which is the thing that was actually true all along — not because the viewport crossed a line I drew.

auto-fit versus auto-fill is the one choice worth understanding here: auto-fit collapses empty tracks so the present cards stretch to fill the row, while auto-fill keeps the empty tracks reserved, so a lone card stays its minimum width instead of ballooning. For a card wall you almost always want auto-fit; for a grid that should hold its column rhythm even when sparse, auto-fill.

None of this makes media queries obsolete — it makes them rare. There are still shifts that are genuinely about the viewport: collapsing a two-pane app layout into a single column, swapping a horizontal nav for a hamburger, changing type scale for small screens. Those are coarse, page-level decisions where a breakpoint is the honest tool. What intrinsic grid removes is the other kind — the per-component "how many across?" queries that were never really about the screen. When the component's own responsiveness matters more than the grid's, container queries take it the last step, letting a card restyle itself based on the width of its slot rather than the page. Grid for how many fit; container queries for how each one adapts; media queries for the whole-page pivots. That division is what took my stylesheet from four breakpoints per grid to roughly none.

Flexbox with flex-wrap gets you part of the way, and for a simple wrapping row it's less machinery. What it can't do as cleanly is keep the columns aligned into a true grid — wrapped flex items size to content and the rows stop lining up. When the alignment across rows matters, grid is the one that holds the structure.

Let the Layout Respond to Space, Not to Guesses About the Screen

Half my media queries existed to answer a question I'd asked backwards. I was describing viewport widths when I meant available space, and the two only agree in the easy case. repeat(auto-fit, minmax(…, 1fr)) asks the real question — how many of these fit right here? — and answers it continuously, so the breakpoints I'd hand-tuned just evaporated. The rule that's left is simple enough to keep: reach for a media query when the page should change shape, and let grid handle how many fit every other time.