CSSContainer Queries (@container)

Container Queries (@container)

Container queries let an element respond to the size of its container — a specific ancestor element — instead of the size of the whole browser viewport. This solves a problem @media cannot: styling a genuinely reusable component that might sit in a wide main column on one page and a narrow sidebar on another, with the exact same viewport width in both cases.

@container vs @media

@media queries only ever see the viewport (or device characteristics) — they have no idea how wide the specific element they're styling actually is. A card component using @media to switch to a horizontal layout above 600px will do so even if that card is squeezed into a 300px-wide sidebar on a 1400px-wide screen, because 1400px passes the media query regardless of the card's real size. @container fixes this by querying the element's actual container box instead of the viewport.

Step 1: Establish a Containment Context

Before any descendant can query a container's size, some ancestor must opt in with container-type, and usually also give itself a name with container-name (or the container shorthand) so queries can target it specifically:

CSS
.card-slot {
  container-type: inline-size; /* size containment along the inline axis */
  container-name: card;        /* optional, but recommended once you have
                                   more than one kind of container nearby */
}

/* shorthand for both at once: */
.card-slot {
  container: card / inline-size;
}

inline-size is by far the most common value — it means "this element's width becomes queryable" (in a standard horizontal writing mode). container-type: size also makes the block dimension (height) queryable, but requires the container to have a definite size in that axis, which is a much less common setup.

Step 2: Write the Container Query

CSS
.card {
  display: grid;
  gap: 8px;
}

/* Query the nearest ancestor named "card" that established containment */
@container card (min-width: 400px) {
  .card {
    grid-template-columns: 120px 1fr; /* switch to a horizontal layout */
    align-items: start;
  }
}

@container card (min-width: 600px) {
  .card {
    grid-template-columns: 180px 1fr;
    gap: 16px;
  }
}
Worked Example: A Card That Adapts to Its Slot

HTML
<div class="main-column">   <!-- wide slot -->
  <div class="card-slot">
    <article class="card">
      <img src="thumb.jpg" alt="" />
      <div>
        <h3>Article title</h3>
        <p>Summary text…</p>
      </div>
    </article>
  </div>
</div>

<aside class="sidebar">     <!-- narrow slot, same component -->
  <div class="card-slot">
    <article class="card">
      <img src="thumb.jpg" alt="" />
      <div>
        <h3>Article title</h3>
        <p>Summary text…</p>
      </div>
    </article>
  </div>
</aside>

The exact same .card markup and CSS automatically renders as a stacked, single-column layout inside the narrow sidebar and a horizontal image-plus-text layout inside the wide main column — without a single JavaScript measurement, and without the component needing to know anything about the page around it. That's the whole point: the component describes its own responsive behavior, wherever it's dropped.

Container Query Length Units

Inside a containment context, you also get container-relative length units — cqw, cqh, cqi, cqb, cqmin, cqmax — the container- query equivalents of viewport units, scaled to the container instead of the viewport:

CSS
.card__title {
  font-size: clamp(1rem, 5cqi, 1.5rem); /* scales with the container's inline size */
}
One of the most significant modern CSS additions
Container queries directly enable truly component-based responsive design — the same building block adapting correctly no matter where a design system or CMS places it. Before this landed, teams either duplicated components per context or reached for JavaScript ResizeObserver hacks to fake the same behavior. Support across current major evergreen browsers is solid; if you must support very old browser versions, wrap enhanced styles in `@supports (container-type: inline-size)`.
  • A container query only ever sees its own established container — not the viewport, and not an unrelated ancestor that never opted in with container-type.

  • Name containers once you have more than one nested containment context, so queries target the intended ancestor unambiguously.

  • container-type: inline-size is the workhorse value for the vast majority of real component layouts.

Next
Make the same component direction-agnostic for RTL layouts too: [CSS Logical Properties](/css/logical-properties).