skip to content

Container Queries

Querying the size of an element's container instead of the viewport, which is what component-based UIs actually need. Expect to explain why a reusable card cannot be made truly responsive with media queries alone.

part ofCSSoverview, primer and where to startread it →
on this pageshow

questions

5

In CSS, how do you make an element a query container named card and apply styles only when that container is at least 400px wide?

level: juniorimportance: must knowfreq 55%

answer

  1. two declarations plus one at-rule
  2. container-type first, name optional
  3. shorthand order is name then type
  4. styles land on descendants
  5. min-width or the range form

basics

~20 s

Set container-type: inline-size and container-name: card on the wrapper, or the shorthand container: card / inline-size, then put the rules inside @container card (min-width: 400px). Those rules style the container's descendants, not the container itself.

solid answer

~40 s

Two declarations and one at-rule. On the wrapper you write `container-type: inline-size` to make it queryable and `container-name: card` to label it, or the shorthand `container: card / inline-size`. Then the component's rules go inside `@container card (min-width: 400px) { … }`. The name is optional — an unnamed `@container (min-width: 400px)` matches the nearest ancestor query container — but naming is useful when containers nest and you want to skip past the closest one. Conditions also accept range syntax, so `@container card (width >= 400px)` is equivalent. The critical detail is directionality: the rules inside the block apply to **descendants** of the container, never to the container element itself, so `container-type` belongs on a wrapper and the selectors inside target the children.

code

css · 15 lines
css
.card-slot {
  container: card / inline-size;
}

.card {
  display: grid;
  gap: 1rem;
}

@container card (min-width: 400px) {
  .card {
    grid-template-columns: 8rem 1fr;
    align-items: start;
  }
}

go deeper

for a junior

Be able to write the pattern from memory: container-type on a wrapper, optional container-name, then an @container block whose selectors target the children.

for a middle

Explain when a name is worth adding, how an unnamed query resolves to the nearest ancestor container, and why the range syntax and min-width forms are interchangeable.

for a senior

Show where the container wrapper belongs in a component's markup contract so the component stays portable, and how you detect support or degrade when the at-rule is ignored.

for a principal

Own the naming vocabulary across a design system — which slots are containers, what they are called, and how nested containers are kept from making component styles unpredictable.

## The three pieces A working container query always has three parts: an element that opts in as a container, an optional name for it, and an `@container` rule whose selectors target that container's descendants. ```css /* 1 + 2: the wrapper opts in and takes a name */ .card-slot { container-type: inline-size; container-name: card; } /* 3: the component's rules, conditional on the container's width */ @container card (min-width: 400px) { .card { grid-template-columns: 8rem 1fr; } } ``` The shorthand collapses the first two declarations, with the name before the slash and the type after it: ```css .card-slot { container: card / inline-size; } ``` The order matters and is easy to invert from memory — it is `name / type`, matching the way `@container <name> (<condition>)` reads. ## container-type: what to pass - `inline-size` — queryable on the inline axis (width in a horizontal writing mode). This is what you want almost always. - `size` — queryable on both axes; only sound when the element has a definite size in both, otherwise it collapses. - `normal` — the default; not a size query container. ## The name is optional Without a name, `@container (min-width: 400px)` resolves against the **nearest ancestor query container** of each element the inner selectors match. That is usually what you want, and unnamed queries keep components portable — the component does not need to know what its slot is called. Names earn their keep when containers nest. Consider a card container inside a page-region container: an unnamed query inside the card will always hit the card. If a rule needs the outer region's width, name the containers and query the outer one explicitly: ```css .region { container: layout-region / inline-size; } .card-slot { container: card / inline-size; } @container layout-region (min-width: 60rem) { … } /* skips past the card */ ``` A container may carry several names (`container-name: card sidebar-slot;`), which lets one element answer to more than one query vocabulary. ## Writing the condition The condition grammar mirrors media queries. Both of these are the same query: ```css @container card (min-width: 400px) { … } @container card (width >= 400px) { … } ``` Range syntax also supports two-sided ranges — `@container card (400px <= width < 700px)` — and conditions combine with `and`, `or` and `not`, plus parentheses for grouping. Because the queried axis is inline under `container-type: inline-size`, height-based conditions such as `min-height` will not match; those require `container-type: size` on a container with a definite height. Inside a container query you can also use container-relative length units, which resolve against the query container rather than the viewport; they are a topic of their own but they pair naturally with these rules. ## The direction of application This is the part that trips people up. The block styles **descendants of the container**, not the container. This does not work: ```css /* broken: .card is the container and can never match its own query */ .card { container-type: inline-size; } @container (min-width: 400px) { .card { grid-template-columns: 8rem 1fr; } } ``` An element matching its own size query would create a circular dependency — the rule could change the width the query just measured — so the specification simply excludes it. The fix is structural: keep a wrapper as the container and style the component inside it. ```html <div class="card-slot"> <article class="card"> … </article> </div> ``` In practice, most component libraries add exactly one wrapper element per placement slot for this reason, and the component itself carries only `@container` rules — which is what makes it portable across pages. ## Feature detection If you need to branch on support, `@supports (container-type: inline-size) { … }` is the correct test. In most products this is no longer necessary; the usual approach is to write the narrow layout as the base styles so an unsupporting browser simply keeps it.

  • What happens if you omit container-name and just write @container (min-width: 400px)?
    The query resolves against the nearest ancestor query container of whatever the inner selectors match. That is the common case and keeps a component portable, because it does not have to know the name of the slot it was placed in. Names are only needed to reach past a closer container to an outer one.
  • Can one element be given more than one container name?
    Yes — `container-name` accepts a space-separated list, such as `container-name: card sidebar-slot;`. The element then matches queries written against either name. It is useful when the same wrapper participates in a component-level vocabulary and a layout-level one.
  • Why does @container card (min-height: 300px) never match a container declared with container-type: inline-size?
    Inline-size containment only makes the inline axis queryable, so block-axis conditions cannot be evaluated and the query does not match. Querying height requires `container-type: size`, which contains both axes — and that is only safe when the container has a definite height, otherwise it collapses.

saying these in an interview costs you the question

  • Writes the shorthand as container: inline-size / card
  • Applies the @container rules to the container element itself
  • Omits container-type and expects @container to work
  • Thinks container-name alone makes an element queryable
  • Queries min-height against an inline-size container

context

open as a page

Why can a reusable card component not be made truly responsive with CSS @media queries alone, and what do container queries change?

level: middleimportance: must knowfreq 68%

basics

~20 s

@media queries only test the viewport, so one card gets the same breakpoint in a wide main column and in a narrow sidebar. Container queries let a component respond to the size of its own container instead.

open as a page

In CSS, what does container-type: inline-size do to an element besides making it a query container?

level: seniorimportance: should knowfreq 40%

basics

~20 s

It applies layout, style and inline-size containment. The element's inline size stops depending on its contents, and it becomes a stacking context and a containing block for absolutely and fixed-positioned descendants. Block size still grows with content.

open as a page

A CSS @container (min-width: 400px) block never applies even though the element is clearly wider than 400px on screen. What are the likely causes?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Usually no ancestor declared container-type, or the element being styled is itself the container — an element cannot match its own query. Other causes: a name that matches no ancestor, a nearer container shadowing the intended one, or a height condition against an inline-size container.

open as a page

What is a CSS style query written as @container style(--variant: promo), and how does it differ from a size container query?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

A style query tests the computed value of a property on the query container rather than the container's size. Every element is a style query container by default, so no container-type declaration is needed, and shipping browsers currently support only custom properties in the condition.

open as a page