Skip to content

The publication for web craftspeople Wednesday, 2 September 2026

Design & UX

Container queries: components that adapt to their container, not the viewport

Container queries let a component respond to the width of its container instead of the browser window. That shift changes how genuinely modular interfaces are built, from a narrow column to full width.

Les container queries : des composants qui s’adaptent à leur conteneur

For twenty years, adapting interfaces relied on a single reference: the width of the browser window. Media queries interrogate the viewport, never the space actually available around a component. Container queries lift that limit by letting an element respond to the size of its container. The same block can then render compact in a sidebar and expanded in the centre of the page, without depending on screen resolution.

The idea: query the container, not the window

The mechanism works in two steps. A parent element is declared as a query container; its descendants can then condition their layout on that container’s size. The declaration uses the container-type property, optionally paired with a name via container-name.

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

/* The card reacts to its container width, not the viewport */
@container card (min-width: 30rem) {
  .card {
    display: grid;
    grid-template-columns: 12rem 1fr;
    gap: 1.5rem;
  }
}

The inline-size value asks the browser to watch the inline dimension, that is the width in horizontal writing, while leaving the height free. It is the most common setting because it avoids the side effects of containing both axes. Like anchor positioning, container queries remove a slab of JavaScript once needed to measure elements.

Container units

Where media queries had viewport-relative units such as vw and vh, container queries introduce their own, computed against the size of the nearest query container. They let you size typography and spacing in proportion to the component.

UnitReferenceViewport equivalent
cqw1% of the container widthvw
cqh1% of the container heightvh
cqi1% of the inline sizevi
cqb1% of the block sizevb
cqminthe smaller of the twovmin
cqmaxthe larger of the twovmax
.card h3 {
  /* Fluid heading, indexed on the container width */
  font-size: clamp(1rem, 4cqi, 1.75rem);
}

One component, two contexts

The payoff shows on a component reused in several places. The same product card, placed in a three-column grid or a full-width banner, takes on two layouts without a single global media query. The markup stays identical; only the container width decides.

<div class="card-container">
  <article class="card">
    <img src="/product.jpg" alt="" />
    <div class="body">
      <h3>Product name</h3>
      <p>Short description.</p>
    </div>
  </article>
</div>
viewportcontainer@container (min-width)
The component reacts to its container width, not to the viewport.

Media queries and container queries: complementary

Container queries do not replace media queries, they complement them. The overall page structure, its layout gutters and its major breakpoints stay driven by the viewport, because they genuinely depend on screen size and device orientation. Container queries take over inside, at the level of reusable components, where the available width varies from one place to another. The two mechanisms coexist in the same stylesheet without interfering: it is up to the author to draw the line, keeping the viewport for overall layout and the container for the behaviour of interface building blocks.

Naming and nesting containers

The container shorthand combines type and name in a single declaration. Naming containers quickly becomes essential once they nest: an unnamed query targets the nearest ancestor container, which is not always the one expected. With an explicit name, the query unambiguously reaches the right level, even across several nested containers. This naming discipline mirrors that of a component system: each major page area, sidebar, main content or card footer, benefits from a stable name reused by the components it hosts.

/* Shorthand: type and name in one declaration */
.sidebar { container: rail / inline-size; }
.main    { container: content / inline-size; }

/* The query targets the nearest named container */
@container rail (max-width: 20rem) {
  .widget { font-size: 0.875rem; }
}

Progressive deployment

The feature is available in recent versions of the major browsers, but a high-traffic site always keeps a share of visitors on older versions. Progressive enhancement answers this case: the @supports rule only turns on the conditional layout where container-type is recognised, and leaves a perfectly readable fallback elsewhere. The component stays functional everywhere, simply richer where the browser allows. This approach avoids the classic all-or-nothing trap, where a recent feature breaks rendering on older setups.

/* Progressive enhancement: only opt in when supported */
@supports (container-type: inline-size) {
  .card-container { container-type: inline-size; }
}

Typography and reflowing content

Beyond layout, container queries refine typography. Combined with the cq* units and the clamp() function, they produce text sizes that follow the component width without abrupt steps. Reading comfort is preserved when the same block moves from a narrow column to full width. One caveat: indexing a font size solely on the container can harm accessibility if the scale ignores the user’s preferences. The good practice is to bound the values with clamp() and keep a base in relative units, so that zoom and system settings are still respected.

What container queries do not do

An element cannot query itself: the query always targets an ancestor container, never the element that declares container-type. Containment also has a rendering cost, best reserved for components that benefit from it rather than the document root. Finally, style queries extend the idea to the container’s custom properties, useful alongside design tokens.

/* Style query: react to a custom property on the container */
@container style(--density: compact) {
  .card { padding: 0.5rem; }
}

A component stops being responsive at the scale of the page and becomes responsive at the scale of its place.

Watch out: declaring container-type: size contains both axes and requires an explicit height, otherwise the content can vanish. In the vast majority of cases, inline-size is the right choice.

The takeaway

Container queries move responsive logic from the document to the component. Together with the cq* units and, soon everywhere, style queries, they make interface libraries truly portable from one context to another. They round out a family of modern CSS features alongside View Transitions, each one trimming the reliance on JavaScript.

For a long time I duplicated components to handle their variants by location. Since I lean on container queries, I keep a single component and let it adapt wherever it sits: my CSS got leaner and so did my code reviews. It is one of the rare changes that simplifies both the code and the architecture. — Simon Janvier

Further reading

Reference documentation: Container queries on MDN.

Read next