DEV Community

Leo
Leo

Posted on Originally published at news.html.to

Container queries observe the component, not the viewport

Drop a card component into a two-column layout. In the wide column it lays out as a row: image left, copy right. Move the same card into a 320px sidebar and it still tries to be a row, because its breakpoint was written against the viewport, not the box it now lives in. That is the pattern Victor Ayomipo, writing in Smashing Magazine on 16 September, is trying to get you to stop shipping.

Where each query lives

Media queries answer the browser. Container queries answer the box.

Ayomipo frames it as "macro" vs "micro" layout. A media query asks the viewport how wide the page is. A container query asks a specific ancestor element how wide it is. Reuse the same breakpoints across both and the component reads the wrong signal.

The State of CSS numbers he cites explain why the mismatch keeps happening: 86% of developers know container queries exist, and 41.4% actually use them. Browser support sits at roughly 94% (caniuse.com), so the gap is not the engines. It's the habit of copying media-query shapes into new syntax.

The two-line wiring

The minimum shown in the source to make a card respond to its own width:

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

@container card (min-width: 450px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}
Enter fullscreen mode Exit fullscreen mode

container-type: inline-size gives the wrapper a size a descendant can query. container-name labels it. Then @container card (...) reads that wrapper's inline size, not the viewport. Drop the card in a 300px rail: the row layout stays off. Drop it in a 600px column: it flips. Same component, two contexts.

One rule to internalise, per the article: you cannot query the element you are styling. The component and its container have to be different nodes, or the layout goes into a loop. That is the reason for the wrapper.

Container units, same idea

Container units are the other half. Ayomipo's typography example:

.card-title {
  font-size: clamp(1rem, .5rem + 3cqi, 2rem);
}
Enter fullscreen mode Exit fullscreen mode

cqi is 1% of the container's inline size. Wired to a container-type: inline-size ancestor, clamp() gives the title a floor, a ceiling, and a smooth ramp between them driven by the box rather than the viewport. The card grows and shrinks with its column. No media-query stack, no per-breakpoint font sizes.

The rough edges

The article walks through a flex-wrap detection pattern for a card grid: flex items sized flex: 1 1 390px are each given container-type: inline-size, then an @container (min-width: 600px) rule flips a card from column to row once it stops sharing its row with siblings. The card notices when it is alone. The viewport is not involved.

Ayomipo also flags the sharp corners. Querying block size can collapse a layout when the container has no explicit height. Custom-property values do not work inside a container-query condition, so a shared --breakpoint variable is out. And the wrapper element is real DOM; a component still cannot query itself.

What to try next

Pick one component on your current project that reads viewport width — a card, a media object, a summary tile. Wrap it, set container-type: inline-size on the wrapper, and rewrite its breakpoints as @container rules with cqi for the type ramp. Drop it in three contexts on the same page. If it renders the same layout in a 300px rail and a 900px column, the breakpoints are still viewport-shaped. Kevin Powell, quoted from a SmashingConf Amsterdam 2026 talk, called container-query adoption "terrible" for exactly that reason.

Top comments (0)