CSS Container Queries: Style Components by Their Own Size

- What Are CSS Container Queries?
- Container Queries vs Media Queries
- Step 1: Create a Container with container-type
- Step 2: Write @container Rules
- Container Query Units: cqw, cqh, and Friends
- Using Container Queries with Sass
- A Real-World Example: One Card, Three Contexts
- Container Queries Browser Support
- Common Mistakes to Avoid
- The Bottom Line
What Are CSS Container Queries?
CSS container queries let you style an element based on the size of its parent container instead of the size of the browser viewport. You mark an ancestor element as a container with the container-type property, then write @container rules that apply styles whenever that container is wider (or narrower) than a threshold you choose.
That one shift solves a problem media queries never could: a component's layout can now respond to the space it's actually given. Drop the same card component into a narrow sidebar and a wide main column on the same page, and it can render as a compact stacked card in one spot and a roomy horizontal card in the other — no extra classes, no JavaScript, no duplicate CSS.
Container queries are supported in all modern browsers and have been safe for production use since early 2023. If you build reusable components — and in 2026, almost everyone does — they belong in your toolkit next to media queries, not as a someday feature.
Container Queries vs Media Queries
Media queries ask, "How big is the viewport?" Container queries ask, "How big is the box this component lives in?" Both are essential; they just operate at different levels of your design.
| Media queries | Container queries | |
|---|---|---|
| Responds to | Viewport (or device features) | A designated ancestor element |
| Best for | Page-level layout: grids, navigation, column counts | Component-level layout: cards, widgets, form rows |
| Setup required | None — works anywhere | An ancestor needs container-type |
| Same component, two contexts | Needs modifier classes or duplicated rules | Adapts automatically to each context |
| Query units | vw, vh, and friends |
cqw, cqh, cqi, cqb, cqmin, cqmax |
| Syntax | @media (min-width: 768px) |
@container (min-width: 400px) |
A practical rule of thumb: use media queries to decide how many columns the page has, and container queries to decide how each component behaves inside its column. The page-level thinking is covered in depth in our responsive design guide; this article handles the component level.
Step 1: Create a Container with container-type
Nothing responds to a container query until you declare a container. You do that on the element whose size you want to measure — typically a wrapper around your component, not the component itself.
.card-wrapper {
container-type: inline-size;
}
container-type accepts three values:
inline-size— the container can be queried by its inline dimension (width, in horizontal writing modes). This is the value you'll use 95% of the time.size— the container can be queried by both width and height. Use sparingly: it requires the element's height to be determined without looking at its contents, which breaks the "height grows with content" behavior most layouts rely on.normal— the default. The element isn't a size query container (though it can still be queried by style queries).
Why inline-size is the safe default
Declaring a query container applies containment to the element. With container-type: inline-size, the element's width can no longer be sized by its contents — it must come from its context, like a grid track, a flex basis, or a plain block-level width. In practice that's how most wrappers already work, which is why inline-size rarely causes surprises. Full size containment also locks down the height, which is why it's the riskier choice.
Naming containers
When containers nest — a card inside a sidebar inside a page — a @container rule resolves against the nearest ancestor container by default. To target a specific one, give it a name:
.sidebar {
container-name: sidebar;
container-type: inline-size;
}
Or use the shorthand, which takes the name first, then the type:
.sidebar {
container: sidebar / inline-size;
}
Step 2: Write @container Rules
With a container declared, @container works much like @media: a condition, then a block of normal rules.
.card {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@container (min-width: 400px) {
.card {
grid-template-columns: 120px 1fr;
align-items: center;
}
}
Here the card stacks by default and switches to an image-beside-text layout whenever its container is at least 400px wide — regardless of what the viewport is doing. Note that this is the same mobile-first pattern you already use with media queries: start with the narrow layout, then enhance upward. If that workflow is new to you, our mobile-first CSS guide explains why it produces simpler stylesheets.
To query a named container, put the name before the condition:
@container sidebar (min-width: 300px) {
.widget {
display: flex;
}
}
Modern range syntax works too, and reads more naturally:
@container (width > 400px) {
.card {
grid-template-columns: 120px 1fr;
}
}
One rule to remember: a container query can't style the container it queries. The styles inside @container apply to descendants of the container. If you need to restyle the wrapper itself, query an ancestor container above it.
Container Query Units: cqw, cqh, and Friends
Container queries ship with their own length units, mirroring viewport units but measured against the queried container:
cqw— 1% of the container's widthcqh— 1% of the container's heightcqi— 1% of the container's inline size (equalscqwin horizontal writing modes)cqb— 1% of the container's block sizecqmin/cqmax— 1% of the smaller / larger of the two dimensions
Their killer use case is fluid typography that scales with the component, not the page:
.card h2 {
font-size: clamp(1.125rem, 4cqi + 0.5rem, 1.75rem);
}
Now the same heading is modest in a cramped sidebar and generous in a wide feature slot, with no breakpoints at all. Prefer cqi over cqw when you can — it respects vertical writing modes for free.
You can use these units anywhere a length is valid, even outside @container blocks. If no ancestor container exists, they fall back to behaving like the corresponding small viewport units, so they degrade sensibly. For a refresher on how all the CSS length units relate, see our guide to CSS units.
Using Container Queries with Sass
Sass passes @container through to the compiled CSS untouched, and nesting works exactly as it does with @media. That means you can keep component variants next to the component:
@mixin container-up($min) {
@container (min-width: #{$min}) {
@content;
}
}
.card {
display: grid;
gap: 1rem;
@include container-up(400px) {
grid-template-columns: 120px 1fr;
}
}
Dart Sass compiles that to:
.card {
display: grid;
gap: 1rem;
}
@container (min-width: 400px) {
.card {
grid-template-columns: 120px 1fr;
}
}
Two Sass-specific notes:
- Use interpolation in the condition. Inside a
@containerrule's condition, wrap Sass variables as#{$min}so the compiled output contains the literal value. - Centralize breakpoints in a map. Just as teams keep viewport breakpoints in a Sass map, component breakpoints (
"card-horizontal": 400px,"widget-wide": 300px) belong in one place so designers and developers share a vocabulary.
A Real-World Example: One Card, Three Contexts
Here's the pattern that sells container queries to every team that tries them. Imagine a product card used in three places: a 300px sidebar, a three-column grid, and a full-width feature slot.
.product-slot {
container-type: inline-size;
}
.product-card {
display: grid;
gap: 0.75rem;
}
/* Enough room: image moves beside the text */
@container (min-width: 380px) {
.product-card {
grid-template-columns: 140px 1fr;
}
}
/* Lots of room: bigger image, roomier spacing */
@container (min-width: 640px) {
.product-card {
grid-template-columns: 220px 1fr;
gap: 1.5rem;
}
}
Every place the card appears, its slot is the container. In the sidebar, neither query matches and the card stacks. In the grid, each column is around 400px, so the first query kicks in. In the feature slot, both match and the last one wins, exactly like the mobile-first cascade you already know. The card's CSS never mentions the sidebar, the grid, or the feature slot — which means next month's brand-new context works without touching this file.
Compare that with the media-query version: you'd need to know every viewport width at which each slot changes size, encode those page-level details into the component's styles, and update them whenever the page layout changes. That coupling is exactly what container queries remove.
Container Queries Browser Support
Size container queries and container query units are supported in every current major browser: Chrome and Edge (from version 105), Safari (from 16), and Firefox (from 110). All of those releases shipped by early 2023, so unless your audience includes very old browsers, you can use container queries today without a build step or polyfill.
Two related features are worth tracking separately:
- Style queries (
@container style(--theme: dark)) let components respond to a container's custom property values rather than its size. Support arrived later and rolled out browser by browser, so check current support tables before relying on them in production. - Progressive enhancement is straightforward when you need it: write your stacked, narrow-friendly layout as the default, and put enhancements inside
@container. Browsers that don't understand the rule simply keep the default layout — the same graceful-degradation story media queries have always had.
Common Mistakes to Avoid
- Forgetting
container-type. An@containerrule with no ancestor container never matches. If your query silently does nothing, this is almost always why. - Making the component its own container. Declare the container on a wrapper. If the element you're styling is the container, the query can't affect it.
- Reaching for
container-type: sizeby habit. Height-based queries force height containment, which collapses elements whose height should come from their content. Start withinline-size. - Skipping names in nested layouts. Unnamed queries resolve to the nearest container, which may not be the one you meant once components nest. Names cost nothing and prevent head-scratchers.
- Recreating page layout with container queries. They complement media queries; they don't replace them. Viewport-level decisions still belong in
@media.
The Bottom Line
Container queries close the gap that made truly reusable components impossible in CSS: components can finally own their responsive behavior. Declare a wrapper with container-type: inline-size, write @container rules the same mobile-first way you write media queries, and reach for cqi units when you want fluid sizing inside the component. Keep media queries for the page grid, hand everything inside it to container queries, and your components will work in every slot you'll ever drop them into — including the ones you haven't designed yet.