webdev.complete
📱 Responsive & Modern CSS
🎨CSS Power
Lesson 18 of 117
20 min

Container Queries

Components that respond to their container, not the viewport.

For twenty years, "responsive design" meant "respond to the viewport." That was always a hack. You don't want your card to know how big the browser is. You want it to know how big itis. A 320px-wide card should look the same in a sidebar as it does on a phone, no matter what the viewport says. That's what container queries are for, and they finally shipped in every evergreen browser by 2023.

The setup: declare a container

Container queries don't come for free. You have to mark an element as a containment context by giving it container-type. The two common values:

  • inline-size: contain in the inline dimension (width, for left-to-right languages). 95% of the time, this is what you want.
  • size: contain in both dimensions. Rarely needed and has more layout side effects.
css
.card-wrapper {
  container-type: inline-size;
}

/* Now any descendant can query the wrapper's width */
@container (min-width: 400px) {
  .card { display: flex; gap: 16px; }
}
@container (max-width: 399px) {
  .card { display: block; }
}

Named containers for clarity

If you have nested containers and need to query a specific ancestor, give it a name with container-name:

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

/* Or the shorthand */
.card-wrapper { container: card / inline-size; }

@container card (min-width: 400px) {
  .card { /* ... */ }
}

When container queries beat media queries

Imagine you build a product card. On the homepage it's in a three-column grid: narrow. In the cart it's a full-width list item: wide. With media queries, you'd need to know the layout context and write different rules. With container queries, the card adapts to whatever space it's given. The same component code works everywhere.

The litmus test
Ask: "does this component change because the page changed, or because itchanged size?" If it depends on the component's width, use a container query. If it's about the page (sidebar collapses, nav becomes a hamburger), media query is still right.

cqi, cqw, cqh: container-relative units

Container queries also added new units that scale with the container, not the viewport. Most useful:

  • cqi: 1% of the container's inline size (width).
  • cqb: 1% of the container's block size (height).
  • cqw, cqh: same as the above but tied to width/height specifically.

Combine with clamp() and you get fluid typography that scales with the component, not the viewport:

css
.card h3 {
  /* font sizes with the card, not the page */
  font-size: clamp(1rem, 4cqi, 1.5rem);
}

A card that adapts to its container

This card has two layouts: a stacked version for narrow containers and a horizontal version when there is room. Drag the container widths around in the HTML by editing the wrapper styles, or just resize the preview pane.

<!doctype html>
<html>
<head><link rel="stylesheet" href="styles.css"></head>
<body>
  <h2>A card in a narrow column</h2>
  <div class="col narrow">
    <article class="card">
      <img alt="" src="https://placehold.co/200x140/4f46e5/ffffff?text=Cover" />
      <div class="body">
        <h3>Bento for One</h3>
        <p>Compact lunch box, three compartments, dishwasher safe.</p>
        <button>Add to cart</button>
      </div>
    </article>
  </div>

  <h2>The same card in a wide column</h2>
  <div class="col wide">
    <article class="card">
      <img alt="" src="https://placehold.co/200x140/10b981/ffffff?text=Cover" />
      <div class="body">
        <h3>Bento for One</h3>
        <p>Compact lunch box, three compartments, dishwasher safe.</p>
        <button>Add to cart</button>
      </div>
    </article>
  </div>
</body>
</html>
container-type has a perf cost
Setting container-type: inline-sizeintroduces layout containment, which is what makes container queries possible. The browser has to lay out the container before its children. This is usually free, but in deeply nested grids it can cost. Don't putcontainer-typeon every element "just in case." Add it where you need it.

Container queries work great with components

The killer use case is design systems. A Card component ships with its own container queries built in. Drop it into any layout (sidebar, modal, three-column grid, full bleed) and it reformats itself. You don't have to pass prop variants like compact={true} from the outside. The component figures it out from context.

Style queries (preview)

There's a sibling feature called style queries for querying CSS custom properties (e.g., "if the--themevalue is dark, do this"). It's rolling out in 2025-2026. Not stable enough to bet a product on yet, but worth knowing about for the future:

css
@container style(--theme: dark) {
  .card { background: #0b1020; color: #e2e8f0; }
}

Quiz

Quiz1 / 4

Before you can query a container's size with @container, what must you do?

Recap

  • Container queries respond to a parent's size, not the viewport. Components become layout-agnostic.
  • Opt in with container-type: inline-size. Name with container-name when you have nesting.
  • Query with @container (min-width: 400px) { ... }.
  • cqi and friends are container-relative units. Combine with clamp() for component-fluid typography.
  • Media queries are still right for layout-wide decisions; container queries are right for component-level ones.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.