Frontend Jul 7, 2026 · 7 min read

Container queries: the day I stopped lying to my components

Yohangel Ramos

Yohangel Ramos

Tech Lead · Senior Fullstack Developer

A reusable component promises something simple: drop it anywhere and it works. For years that promise was a lie, and the lie had a name: media query. A card that adapts with @media (min-width: 768px) doesn't react to its own space, it reacts to the window width. Put it in a narrow sidebar with the screen at 1400px and the card thinks it has room to spare, because it's looking at the wrong variable. Container queries fix this at the root: the component responds to the size of its container, not the viewport. I adopted this in a real product's design system and it saved me the most frustrating class of bug to maintain — the "looks fine in the demo, broken in its real spot" kind. This article is why the shift matters more than it seems.

The media query lie

The problem is one of abstraction level. A well-built component knows nothing about the outside world: it takes props, renders, and is supposed to adapt to wherever you place it. But a media query breaks that encapsulation entirely, because it queries a global state —the viewport width— that the component neither controls nor should know about.

The result is that the same component needs to know which layout it's going to live in. The card that looks perfect in the main three-column grid breaks in the sidebar, not because it's poorly built, but because its breakpoint assumes a window width that doesn't match the width it actually has. You end up adding props like variant="compact" to tell it by hand what it should be able to figure out on its own. Every one of those props is a leak in the abstraction.

How it works: the container declares, the child queries

The mechanism has two parts. First you declare that an element is a query container. Then, inside it, you query its size instead of the screen's.

.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

.card {
  display: grid;
  grid-template-columns: 1fr;
}

@container card (min-width: 400px) {
  .card {
    grid-template-columns: 120px 1fr;
  }
}

What changes from a media query is subtle and huge at once: @container card (min-width: 400px) doesn't ask "is the screen wide?" but "is my container at least 400px?". The same card, without a single extra prop, renders in one column inside a 300px sidebar and in two columns inside a wide grid. The component finally adapts to its reality, not to an assumption about the window.

The case that convinced me: one card in three different places

In the design system I had a ProductCard that appeared in three contexts: the catalog grid (wide), a recommendations carousel (medium), and a "recently viewed" sidebar (narrow). With media queries I had three variants and an if in the consumer deciding which to use based on where it went. Three code paths for a single card.

With container queries I deleted the variants. The ProductCard became a single implementation that queries its container and reorganizes on its own. The component stopped needing context about its location, which is exactly what a component should not need. Fewer props, fewer branches, less surface where something can drift out of sync.

💡 If your component needs a variant prop just to fit different widths, you don't have an API design problem: you have a media query where a container query should be. Fix it at the bottom and the prop disappears by itself.

Trade-offs I accepted

None of this is free. Declaring container-type: inline-size creates a size containment context, and that has a consequence you have to understand: the container stops sizing itself to its content along the queried axis. In practice this rarely bites if you wrap with a dedicated wrapper instead of turning an element that already did other things into a container, but it's a real footnote, not a detail you can ignore.

I also accepted a bit more nesting in the markup. The clean pattern is a wrapper that is the container and a child that is what gets styled; querying an element's size while also styling that same element with the query doesn't work well. One extra div per component is a price I pay gladly in exchange for deleting a whole category of context props.

Why this is more than a convenience

What really changed wasn't the CSS, it was where the responsibility lives. With media queries, the responsibility for a component looking good is split between the component and whoever places it: the consumer has to pick the right variant for the slot. With container queries, the responsibility goes back entirely to the component, which is where it belongs.

That's the difference between a design system that scales and one that turns into a catalog of special cases. Every time a component needs to know where it goes to look right, you've created a coupling between the piece and its context. Container queries cut that coupling, and with it, the kind of debt that never shows up in a ticket but slows you down on every new screen.

Yohangel Ramos

Written by Yohangel Ramos

Senior Fullstack Developer and Tech Lead. I build with React, Next.js, Nest.js and AWS — and I write about what I learn along the way.

Let's talk →

Keep reading

IA

AI-built startups in 2026: the real numbers behind the hype

IA

How to survive as a programmer in the AI era (without turning cynical or naive)