Command Palette

Search for a command to run...

Hectal
PHASE 9Intermediate Baseline 2023+ ~27 min· topic 6 of 7

Topic 9.6

Container Queries

In one line

Container queries let a component respond to the size of its own container instead of the whole window: mark an ancestor with container-type: inline-size, then write @container (min-width: 400px) { ... }, and size things with container units like cqi.

Think of it like this

Packing a suitcase. You don't pack based on the size of your house; you pack based on the size of the bag in front of you. In a big suitcase, shirts lie flat side by side; in a small backpack, you roll them up and stack them. A card on a web page is the same: it should arrange itself based on the space it was given (its container), not on the size of the whole browser window.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Container query
An @container rule that applies when an ancestor container matches a size condition.
Query container
An element marked with container-type, which its descendants can query.
`container-type`
inline-size (query width), size (width and height) or normal (not a size container).
`container-name`
A name for a container, so @container name (...) can target it specifically.
Inline size
The size along the text direction; the width in normal horizontal writing.
Containment
A promise to the browser that an element's size doesn't depend on its contents, so it can measure it first.
`cqi` / `cqw`
Container query units: 1% of the container's inline size / width.
Style query
@container style(--x: y): a query on a custom property's value instead of a size.

Step by step

01Mark the container

Wrap the component and make the wrapper a container. The wrapper is a normal block, so it takes its parent's width: safe for containment.

index.htmlwhole filehtml
<div class="card-slot">
  <article class="card">
    <img src="chai.jpg" alt="A cup of masala chai" width="400" height="300">
    <div class="body">
      <h3>Masala chai</h3>
      <p>Tea, milk and warm spices.</p>
    </div>
  </article>
</div>

02Write the narrow layout first

Like mobile first, the base is the small version: image on top, text below.

style.csswhole filecss
.card-slot {
  container-type: inline-size;
}
.card {
  display: grid;
  gap: 0.75rem;
}
.card img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

03Add the wide layout with @container

When the slot is at least 400px wide, put the image beside the text. The card does this in a sidebar, in a grid cell or full width: it only cares about its slot.

style.csswhole filecss
@container (width >= 400px) {
  .card {
    grid-template-columns: 40% 1fr;
    align-items: center;
  }
}

04Name containers when they nest

A query looks at the nearest container ancestor. If containers nest (a page layout container around a card container), name them so each query asks the right one.

style.csswhole filecss
.sidebar { container: sidebar / inline-size; }
.card-slot { container: card / inline-size; }

@container sidebar (width < 250px) {
  .sidebar .promo { display: none; }
}
@container card (width >= 400px) {
  .card { grid-template-columns: 40% 1fr; }
}

05Scale text with the container

Container units make type follow the component. Here the title grows with the card's width.

style.csswhole filecss
.card h3 {
  font-size: clamp(1.1rem, 0.8rem + 2cqi, 1.6rem);
}

06Media query vs container query

The same window, two answers: a media query sees one viewport; container queries see each slot.

Media query vs container querydiagram
Rendering diagram…

Try it yourself

  1. 1

    Move the breakpoint

    In the first example, change 300px in the @container rule to 450px. Predict both console lines before you run. (Both become column, because the wide slot is only 420px.)

  2. 2

    Try a media query instead

    In the first example, change @container (width >= 300px) to @media (width >= 300px). Predict: do the two cards still differ? (No: both see the same viewport.)

  3. 3

    Fix the trap

    In the last example, remove display: inline-block from .tag. Predict whether the container still collapses. (No: a block takes its parent's width, which doesn't depend on its content.)

Code & diagrams

One card, two slots New tab

The same HTML and CSS, at the same window width, gives two layouts, because each card asks its own slot. The wide slot may stick out of the narrow preview; scroll sideways to see it.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

slot narrow: column
slot wide: row
Resize a container and watch New tab

The script tries three widths and reads the result, then hands the box back to you: drag its bottom-right corner to resize it yourself. The label and its size change with the box, not the window. The font size is 0.5rem + 4% of the box's content width (8px + 7.2px at 180px), capped at 1.6rem (25.6px).

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

180px: "small panel" 15.2px
300px: "medium panel" 20px
500px: "large panel" 25.6px
The zero-width trap New tab

An `inline-block` normally takes the width of its text. With inline-size containment its width is calculated as if it were empty, so only its padding and border remain (18px) and the text spills out. Make the parent the container instead.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

normal: has width
container: collapsed to 18px

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Querying the container itself

Make .card the container (container-type: inline-size) and write @container (width >= 400px) { .card { flex-direction: row; } }.

terminal
$ Page
── what you'll see ──
The card never changes direction at any width, although its children respond correctly.

Break #2

A flex item container that collapses

In a display: flex row, give one child container-type: inline-size and no flex-basis or width.

terminal
$ Page
── what you'll see ──
That item shrinks to almost nothing and its content overflows across its neighbours.

Myth vs fact

Myth

Container queries replace media queries.

Fact

They complement them. Page layout and user preferences (dark mode, reduced motion) still use media queries; components use container queries.

Myth

Every element can be queried by its children.

Fact

Only elements with container-type set to inline-size or size are size containers. Without one, @container queries never match.

Myth

container-type: size is the more complete choice, so use it.

Fact

size contains the height too, so a container whose height comes from its content collapses to zero height. Use inline-size unless you really need to query height and the container has a set height.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Size containment is what makes container queries possible without layout loops: the browser lays out the container's size first, then evaluates @container rules for descendants, then lays out the inside. container-type: inline-size also applies layout and style containment, so the container becomes an independent formatting context (floats and child margins stay inside it) and CSS counters inside it are scoped to it.

  • ▸

    Browser support: Chrome/Edge 105 (2022), Safari 16 (2022) and Firefox 110 (February 2023), making size queries Baseline 2023. Container units arrived at the same time. Style queries for custom properties shipped in Chromium (2023) and Safari (2024); check current Firefox support before using them.

  • ▸

    The @container prelude accepts and, or and not, like media queries: @container card (width >= 400px) and (height >= 300px) (height needs container-type: size). There's also an orientation and aspect-ratio feature for size containers.

  • ▸

    Design systems benefit most: a component can ship with its own responsive rules and no knowledge of the page grid. Combine with cqi-based fluid type for components that scale smoothly in any slot.

Remember this

  1. 1

    Media queries (Topic 9.2) only know the viewport. But the same card component might appear in a wide main column, a narrow sidebar and a three-column grid on the same page, at the same window width. A media query gives all three the same answer. A container query asks about a container: an ancestor element you have marked as one. The card then adapts wherever you put it. This is what makes components truly reusable.

  2. 2

    Step one: make an element a query container with container-type. inline-size (the usual choice) lets children query its inline size, which is its width in horizontal text. size lets them query both width and height, but then the container's height can't come from its content, so you must give it a height. normal (the default) is not a size container. You can name it with container-name: card, or use the shorthand container: card / inline-size.

  3. 3

    Step two: write the query. @container (min-width: 400px) { .card { grid-template-columns: 120px 1fr; } } applies when the nearest ancestor container is at least 400px wide. Range syntax works too: @container (width >= 400px). With a name, @container card (width >= 400px) skips other containers and asks the nearest one called card. A key rule: an element can't query itself. The query always looks at an ancestor, so you style the children of the container, often with a wrapper element as the container.

  4. 4

    Why the setup step? To answer "how wide is the container", its width must not depend on its children, otherwise a child changing layout could change the container's width, which would change the query answer, which would change the layout again: an infinite loop. container-type: inline-size applies inline-size containment: the container's width is calculated as if it had no content. In a normal block that's fine, because blocks take the width of their parent anyway. But a container that shrinks to fit its content (a float, an inline-block, a flex item with no set size, a fit-content box) collapses to zero width. That is the most common container query bug.

  5. 5

    Container query units size things relative to the query container: cqw (1% of its width), cqh (1% of its height), cqi (1% of its inline size), cqb (1% of its block size), cqmin and cqmax (the smaller or larger of the two). font-size: clamp(1rem, 0.5rem + 3cqi, 2rem) gives a heading that scales with its card, not the window. If no eligible container exists, the units fall back to the small viewport units (svw, svh…).

  6. 6

    Container queries became Baseline in 2023 (Chrome and Edge 105, Safari 16, Firefox 110). They don't replace media queries: use media queries for page-level decisions ("show the sidebar on wide screens") and container queries for components ("this card goes horizontal when it has room"). Newer style queries (@container style(--theme: dark)) test custom property values on the container; check browser support before relying on them, as Firefox support arrived later than the size queries.

Explain it without notes

01

What problem do container queries solve that media queries can't?

02

What does container-type: inline-size do, and why is it required?

03

Why can a container collapse to zero width, and how do you avoid it?

04

What are container query units, and what do they fall back to?

Practice

01

Make a .profile component stack its avatar above its text by default and place them side by side when its container is at least 360px wide.

02

Inside a container named widget, hide .extra when the widget is narrower than 250px. Make sure a nested container doesn't answer instead.

03

Make a card title scale with the card width between 1rem and 1.75rem.

Trade-offs

  • ↔

    Container queries make components portable, but each container adds containment, which can cause collapsing bugs and needs an extra wrapper element.

  • ↔

    Container units scale nicely with components but don't respond to zoom the way rem does; keep a rem part in clamp() as with vw.

Done when you can

  • I can create a container with container-type or the container shorthand.

  • I can write @container queries, named and unnamed, with range syntax.

  • I know an element can't query itself.

  • I can recognise and fix a container collapsing to zero width.

  • I can use cqi and other container units.

  • I can choose between a media query and a container query.