Loading...

CSS Container Queries

Nesting kept component selectors in one place. Media queries still watch the viewport. Container queries answer a different question: how wide is this parent box? That is how one product card can go compact in a narrow rail and open up in a wide main column — even when the browser window never changed.

Why the viewport is not enough

A layout often places the same component in unequal slots: a 16rem sidebar rail and a fluid main area. Both slots share one viewport width, so @media (min-width: …) fires the same way for both. You need styles that key off the container that wraps the card, not the whole screen.

Make a containment context

Pick the parent that should report its size. Set container-type — usually inline-size so queries can use the parent’s inline size (width in horizontal writing modes) without forcing a full size containment on both axes:

Turn a rail into a size container

css
.rail {
container-type: inline-size;
container-name: rail;
width: 16rem;
padding: 1rem;
background: #f8fafc;
}

.stage {
container-type: inline-size;
container-name: stage;
flex: 1;
min-width: 0;
padding: 1.25rem;
}

Without container-type, @container has nothing reliable to measure. container-name is optional but powerful when several ancestors are containers — you can aim at one by name.

Query the container with @container

Inside (or after) that setup, wrap rules in @container with a size condition. Descendants of the container respond when that box matches — not when the viewport does:

Product card reacts to its parent width

css
.product-card {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
background: #fff;
}

.product-card .media {
aspect-ratio: 16 / 10;
border-radius: 0.5rem;
background: #cbd5e1;
}

.product-card .meta {
font-size: 0.875rem;
color: #64748b;
}

@container (min-width: 28rem) {
.product-card {
grid-template-columns: 10rem 1fr;
align-items: center;
}

.product-card .title {
font-size: 1.25rem;
}
}

When .product-card lives in a narrow .rail, the single-column stack stays. Drop the same markup into a wide .stage and the two-column layout unlocks — same HTML, same viewport, different container width.

Named containers

If nested containers confuse which box is queried, pass the name after @container:

Target a named containment context

css
@container stage (min-width: 36rem) {
.product-card {
grid-template-columns: 12rem 1fr auto;
gap: 1.25rem;
}

.product-card .buy {
justify-self: end;
}
}

@container rail (max-width: 18rem) {
.product-card .meta {
display: none;
}
}

@container stage (…) only cares about the ancestor named stage. That keeps a rail’s tight rules from fighting a stage’s roomy rules when both wrap related markup.

Container query units

Once a containment context exists, length units like cqi (1% of the query container’s inline size) and cqw (1% of its width) let type and spacing scale with the parent, not the viewport (vi / vw):

Type that tracks the container

css
.product-card .title {
font-size: clamp(1rem, 0.85rem + 2.5cqi, 1.5rem);
}

.product-card {
padding: clamp(0.75rem, 2.5cqi, 1.5rem);
}

Pair these with var() and clamp() from earlier Modern Toolkit lessons. Container units measure the containment context used for query — ideal for reusable widgets.

Container vs media — who does which job?

📊 Container queries vs media queries

Question you are asking Prefer Typical tool
How wide is the browser viewport? Page shell, nav, multi-column page @media (min-width: …)
How wide is this parent around a component? Cards, widgets, embeds in unequal slots @container (min-width: …) after container-type
Do I need both? Often yes MQ for page; CQ for the component inside a slot

Twin slots, one component

A small layout sketch shows the payoff — two containers, one card class:

Sidebar rail and main stage share one card

css
.page {
display: flex;
gap: 1rem;
align-items: start;
}

.rail {
container-type: inline-size;
container-name: rail;
width: 16rem;
}

.stage {
container-type: inline-size;
container-name: stage;
flex: 1;
min-width: 0;
}

/* Same .product-card class in both slots — @container rules above decide layout */

You do not fork .product-card--sidebar and .product-card--main only to mirror slot width. The slot reports its size; the card listens.

Pair page media with component containers

Keep viewport queries for page decisions (collapse a site nav, switch the whole shell to one column). Keep container queries for component decisions inside whatever slot the page gave them:

Viewport for the page, container for the card

css
@media (max-width: 40rem) {
.page {
flex-direction: column;
}

.rail {
width: auto;
}
}

@container (min-width: 28rem) {
.product-card {
grid-template-columns: 10rem 1fr;
}
}

On a small phone the page stacks; on a wide desktop the rail may still be narrow while the stage is wide — only container queries express that split cleanly.

Try it yourself

  1. Add container-type: inline-size to a wrapper around a card; confirm DevTools shows a containment context.
  2. Write @container (min-width: 24rem) to switch the card from stacked to two columns.
  3. Set container-name: panel and retarget with @container panel (min-width: 30rem).
  4. Size a heading with cqi and resize only the parent (not the window) to see type move.
  5. Leave one @media on the page shell; refuse to duplicate that logic inside the card.

Common mistakes

  • Writing @container without container-type on an ancestor — nothing useful to query
  • Replacing every media query with container queries — page shell still belongs to @media
  • Nesting three containers and wondering which width won — name them and target explicitly
  • Copying viewport breakpoints (48rem) blindly into every widget — tune to the slot, not the device catalog
  • Confusing nesting (&, nested selectors) with containment — nesting organizes selectors; container queries measure boxes

Summary

  • Container queries style from a parent’s size, not only the viewport
  • Set container-type (often inline-size); optional container-name for clarity
  • Use @container (min-width: …) / named @container name (…) to restyle descendants
  • Reach for cqi / cqw when lengths should track the container
  • Next in Modern Toolkit: feature queries (@supports) — gate styles on browser capability

🧠 Test Your Knowledge

Ready to Start

Test Your Knowledge

Challenge yourself with this interactive quiz and see how well you understand the topic

❓
6
Questions
🎯
70%
To Pass
♾️
∞
Time
🔄
∞
Attempts

📝 Instructions

  • Read each question carefully
  • Select the best answer for each question
  • You can retake the quiz as many times as you want
  • Your progress will be shown at the top