CSS / UI

CSS Container Queries: The Feature I Wish I Had Years Ago

3 min read

For most of my career, one CSS problem kept reappearing in every design system I built: components don't know how much space they have. A card that looks perfect in the main column gets crushed in the sidebar; the "fix" is a .card--sidebar modifier class, and three years later the codebase has forty context-specific variants that all mean "this component guessed wrong about its surroundings." Container queries end that era, and they're the rare CSS feature that changes how you architect, not just how you style.

The problem: components are context-blind

Media queries answer one question: how wide is the viewport? But a component doesn't live in the viewport — it lives in a slot: a sidebar, a modal, a dashboard cell, a main column. On a 1400px screen, the same card might have 900px in the content area and 280px in the sidebar. A media query sees only "1400px" and styles both identically — which is exactly backwards, because the narrow sidebar card needs the compact layout your mobile breakpoint defines, and it's never going to get it.

Design systems felt this hardest: a truly reusable component can't know in advance every context it'll be dropped into, so it can't be responsive via media queries by definition.

Container queries: the component asks about its own space

The mechanism has two halves. First, an ancestor declares itself a measurable container:

.sidebar,
.main-column {
  container-type: inline-size; /* "children may query my width" */
}

Then any descendant styles itself against that container's size instead of the viewport's:

.card {
  display: grid;
  grid-template-columns: 96px 1fr; /* thumbnail beside text */
  gap: 1rem;
}
 
/* When my container is narrow — wherever that container is: */
@container (max-width: 360px) {
  .card {
    grid-template-columns: 1fr; /* stack: thumbnail above text */
    gap: 0.5rem;
  }
 
  .card .meta {
    font-size: 0.875rem;
  }
}

Now the same card renders side-by-side in the 900px main column and stacked in the 280px sidebar — on the same screen, with zero modifier classes, and it keeps being right when someone drops it into a context you never anticipated. The component owns its responsive behavior; the page stops micromanaging it.

Two supporting details worth knowing: container-name lets you target a specific ancestor (@container sidebar (max-width: 360px)) when nesting gets deep, and container query units (cqw, cqi — percentages of container width) do for component-internal sizing what viewport units do for pages, pairing beautifully with the fluid clamp() patterns I've written about before.

So what are media queries still for?

The division of labor becomes clean: media queries describe the device; container queries describe the layout. Page-level structure (sidebar becomes drawer, nav collapses) and user preferences (prefers-reduced-motion, prefers-color-scheme) remain media-query territory — they're genuinely about the viewport and the user. Everything about how a component arranges itself is now container territory. In new code I write, component CSS essentially never mentions the viewport anymore.

Browser support reality check

The good news: container size queries have been supported in every major browser since early 2023 — this is not an experimental feature, and for evergreen-browser audiences you can use it unguarded. If your analytics show a meaningful legacy tail, the fallback strategy is straightforward because container queries degrade gracefully by nature: write the mobile-first single-column layout as the base (old browsers get a safe, readable card everywhere), and put the enhanced multi-column arrangement inside @container blocks, optionally wrapped in @supports (container-type: inline-size) for tidiness. No polyfill needed for most products — just a sensible base state.

When to reach for this

Immediately, for anything that lives in multiple contexts: cards, media objects, stat tiles, form rows, table toolbars — design-system components above all. My rule now: if I'm about to write a variant class whose real meaning is "the narrow version," that's a container query. After years of components guessing about their surroundings, letting them simply ask feels like the way CSS should have always worked — which is the highest compliment a new feature can earn.

Found this useful?

Share

I post shorter takes on css, ui & design and frontend leadership on LinkedIn — follow along there, or get in touch if you're working on something related.