Container Queries
Understand @container queries as a component-driven alternative to media queries — letting an element respond to the size of its own container rather than the size of the whole viewport.
Media queries answer the question 'how big is the browser window?' — but a component doesn't always live at full viewport width. A card might be full-width on one page and squeezed into a narrow sidebar on another; a media query can't tell the difference, because it only sees the viewport, not the component's actual available space. Container queries solve exactly this problem, letting a component adapt to the size of the container it's actually placed in. This is a newer CSS feature — modern browsers support it well, but it's worth checking support for any project targeting older browsers.
The problem container queries solve
Imagine a .product-card component. On a product listing page it might sit in a wide grid cell, 400px across. On the homepage it might be squeezed into a narrow 'recommended for you' sidebar, only 220px across. A media query based on viewport width can't distinguish these two situations — the browser window is the same width either way. The card would need to look good at 220px in one context and 400px in another, with no way for a media query to tell them apart.
Container queries flip the reference point: instead of asking 'how wide is the viewport', you ask 'how wide is the direct container this element is sitting in'. The exact same .product-card component can then have one layout when its container is narrow and a different layout when its container is wide — regardless of the overall page or window size.
Declaring a containment context
Before a container query can reference an element's ancestor, that ancestor must opt in with the container-type property. container-type: inline-size is the most common value — it means 'this element establishes a container based on its size along the inline (normally horizontal) axis'. You typically also give it a container-name so child queries can target it specifically, though a name isn't strictly required.
Once a container is declared, descendant elements can use @container — very similar syntax to @media, but scoped to that named container's size rather than the viewport.
Component-driven responsiveness
This shift matters philosophically as much as technically: media queries make the PAGE responsive (based on the viewport); container queries make the COMPONENT responsive (based on wherever it's dropped in). This is a much better fit for component-based design systems, where the same card, panel, or widget component gets reused in many different layout contexts across a site, and each instance should look right in its own available space without the component author needing to know in advance where it will be used.
Media queries vs. container queries: when to use which
These two tools are complementary, not competitors — most real projects use both. Reach for a media query when you're making a decision about the overall page structure: should the sidebar be visible at all? Should the whole layout switch from a mobile nav to a desktop nav? These are viewport-level questions with a single right answer for the whole page.
Reach for a container query when you're styling a self-contained, reusable component that might appear in different-sized contexts across the site, and its internal layout should adapt to its own available space rather than the whole window — a card, a widget, a sidebar module, anything meant to be dropped into varying contexts.
• Media query → viewport-level, page-structure decisions (nav layout, overall grid, whether a sidebar exists) • Container query → component-level decisions (how a single reusable card/widget rearranges its own internals) • It's normal and common to use both in the same stylesheet, at different scopes
A note on browser support
Container queries reached broad support across major modern browsers relatively recently compared to media queries, which have been supported for well over a decade. For most current projects targeting up-to-date browsers this is a non-issue, but if a project must support older or less common browsers, check current support data (e.g. on caniuse.com) before relying on container queries for anything critical, and consider whether a media-query-based fallback layout is needed.
Declaring a container and querying it
container-type: inline-size measures the container's own width, independent of viewport width. container-name is optional but recommended once you have more than one nested container, so queries target the right one. The shorthand container: card / inline-size; combines name and type in one declaration.
/* 1. Opt an ancestor element into being a query container */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 2. Query that container's size from a descendant */
@container card (min-width: 400px) {
.product-card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
@container card (max-width: 399px) {
.product-card {
display: block;
}
}Code Examples
The exact same .card markup stacks vertically when placed inside a narrow container (like a sidebar) and switches to a horizontal row layout the moment its own container reaches 350px — regardless of how wide the overall browser window is.
@container (min-width: 350px) { .card { flex-direction: row; } }
Container query length units (cqi, cqw, cqh, etc.) work like viewport units (vw, vh) but are relative to the query container instead of the viewport — letting text and spacing scale with the component's own size.
.sidebar, .main-grid-cell {
container-type: inline-size;
}
.card {
display: flex;
flex-direction: column;
}
@container (min-width: 350px) {
.card {
flex-direction: row;
gap: 1rem;
}
}.card-wrapper { container-type: inline-size; }
.card__title {
/* 5% of the container's width, not the viewport's */
font-size: clamp(1rem, 5cqi, 1.5rem);
}Media query vs. container query reference point
Media queries always measure the browser viewport; container queries measure a specific ancestor element.
MEDIA QUERY CONTAINER QUERY
┌───────── viewport ─────────┐ ┌───── viewport ─────┐
│ measures THIS width │ │ ┌──container──┐ │
│ │ │ │ measures │ │
│ │ │ │ THIS width │ │
│ │ │ └──────────────┘ │
└─────────────────────────────┘ └─────────────────────┘Important Rules
An element must have container-type set (commonly inline-size) before its descendants can query it with @container.
@container syntax mirrors @media — min-width, max-width, and combinations with and all work the same way.
Use media queries for page/viewport-level layout decisions; use container queries for reusable, self-contained components.
Container query units (cqi, cqw, cqh, cqmin, cqmax) size relative to the container, analogous to vw/vh for the viewport.
Check current browser support before depending on container queries for critical layout in a project that must support older browsers.
Where this matters in practice
Design systems and component libraries (think reusable card, alert, or widget components used across many products) benefit enormously from container queries because the same component genuinely does get dropped into wildly different layout contexts — a full-width hero slot on one page, a cramped sidebar on another. Before container queries existed, teams often had to create multiple variants of the same component (.card--small, .card--large) manually; container queries let one component adapt automatically.
@container (min-width: 400px) { .card { ... } } /* with no ancestor declaring container-type */
Give a wrapping ancestor container-type: inline-size; first, then write the @container rule for its descendants.
A @container query does nothing unless some ancestor element has explicitly opted in as a query container — unlike @media, which needs no setup because the viewport is always implicitly available.
Using a container query to decide whether the whole page's sidebar should be visible
Use a media query for whole-page structural decisions; reserve container queries for how an individual component rearranges itself
Container queries answer 'how big is my container', which is the wrong question for page-wide layout decisions that should instead be based on total available viewport space.
[Multiple Choice] A .card component looks correct on the product listing page but broken when reused inside a 220px-wide sidebar, even though its internal CSS uses @media (min-width: 400px) to switch to a two-column layout. What's the most likely fix? A. Lower the media query's min-width value to 220px B. Replace the media query with a container query, since the issue is the component's own available width, not the viewport width C. Add !important to force the two-column layout everywhere D. There is no fix; components cannot be reused in different-sized containers
B. Replace the media query with a container query, since the issue is the component's own available width, not the viewport width — The media query is checking viewport width, which may well be 1400px even though the sidebar itself is only 220px — the component has no way to know its own cramped space via a media query. A container query, scoped to the sidebar as the container, would correctly detect that only 220px is actually available.
You have a .stat-widget component that should show its number and label side-by-side when its container is at least 250px wide, and stacked vertically when narrower. Write the CSS needed, including the container declaration..widget-slot { container-type: inline-size; }.stat-widget { display: flex; flex-direction: column; } @container (min-width: 250px) {.stat-widget { flex-direction: row; align-items: center; gap: 0.5rem; } }
You need three pieces: container-type on the wrapper, a base (narrow/stacked) style for .stat-widget, and an @container min-width rule that switches it to a row layout.