CSS Container Queries: How to Build Truly Responsive Components in 2026
For twenty-five years, responsive design meant one thing: media queries that react to the size of the browser window. You wrote breakpoints, at 768 pixels do this, at 1024 do that, and your whole layout responded to the viewport. It worked, but it had a deep flaw that everyone quietly worked around: a component had no idea how much space it actually had. A card in a narrow sidebar and the same card in a wide main column both saw the same viewport, so they got the same styles, even though one had 300 pixels to work with and the other had 800. Container queries fix that at the root. They let a component respond to the size of its container instead of the whole window, which is what responsive design should have meant all along.
This guide explains what container queries are, why they change how you build layouts, and how to use them with real code. It is written for anyone building components, in a WordPress block theme, a framework, or plain HTML, who is tired of components that only know about the window.
The problem with media queries
Media queries respond to the viewport, the browser window, and nothing else. That is fine for page-level layout, but components do not live at the page level; they live inside columns, sidebars, grids, and cards, each with its own width. A media query cannot see any of that. So a card that should switch to a two-column layout when it has room has to guess, based on the viewport, when it will have room, and that guess is wrong the moment the same card appears in a different context. The result is the pattern every developer knows: extra modifier classes like card--sidebar and card--wide, duplicated styles, and components that break when you move them somewhere new. The component’s appearance depended on where it was placed, and you had to hardcode every placement.
What container queries actually do
A container query lets an element respond to the size of an ancestor container rather than the viewport. You mark an element as a container, then write queries against its size, and any descendant can style itself based on how much space that container has, wherever it sits on the page. The same component, dropped into a narrow sidebar or a wide main area, styles itself correctly in both, with no modifier classes and no knowledge of the page layout. The component becomes genuinely self-contained: give it space and it expands, cramp it and it adapts, and it does the right thing every time because it is looking at its real available width.
/* 1. Mark an element as a container */
.card-wrapper {
container-type: inline-size;
}
/* 2. Style the card based on the WRAPPER's width, not the viewport */
@container (min-width: 400px) {
.card {
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
}
Read that carefully: the @container query asks about the width of the nearest ancestor marked as a container, not the browser. Put this card in a 300-pixel sidebar and it stays single-column; put it in an 800-pixel main area and it goes two-column, from the exact same CSS, because it is responding to its container.
Setting it up: the two pieces
Using container queries takes two steps, and understanding both avoids the common confusion. First, you declare a container with container-type. The most common value is inline-size, which means the container is queryable by its width (the inline dimension). Second, you write @container rules that style descendants based on that container’s size. It helps to name your containers so queries target the right one when you have nested containers:
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
@container sidebar (min-width: 300px) {
.widget { /* styles when the sidebar is at least 300px wide */ }
}
Naming is optional but worth the habit, because without it a query targets the nearest container, which is not always the one you meant. With a name, you are explicit about which container’s size you are reacting to.
Before and after: the modifier-class trap
To feel the difference, look at the old approach. To make a card work in both a sidebar and a main column with media queries, you ended up with placement-specific classes and duplicated rules:
/* The old way: a class per placement */
.card { /* base single-column */ }
.card--wide { grid-template-columns: 1fr 1fr; } /* remember to add in main */
.card--sidebar { /* stay single-column */ } /* remember to add in sidebar */
Every time you placed the card, you had to remember which modifier to apply, and if you moved it, you had to change the class. The component did not know its context, so you encoded the context by hand, everywhere, forever. The container-query version deletes all of that:
/* The new way: one card, no modifiers */
.card-wrapper { container-type: inline-size; }
.card { /* base single-column */ }
@container (min-width: 400px) {
.card { grid-template-columns: 1fr 1fr; }
}
Now there is one card. Place it anywhere; it decides its own layout from the space it is given. The modifier classes, and the bugs that came from forgetting them, are simply gone. That deletion of context-juggling is the everyday payoff of container queries.
Container query units: sizing relative to the container
Container queries bring their own units, and they are quietly powerful. Just as vw sizes things relative to the viewport, container query units size things relative to the container. The most useful is cqi, one percent of the container’s inline size, which lets you scale padding, font size, or spacing to the component’s own width rather than the window’s:
.card-title {
/* font size scales with the CARD's width, not the viewport */
font-size: clamp(1rem, 5cqi, 2rem);
}
This is the piece that makes components truly fluid. A heading sized in cqi grows and shrinks with its container, so the same card looks proportionate whether it is narrow or wide, with no breakpoints at all. Combined with clamp(), you get smooth, self-scaling components that respond to their real space.
Real components that benefit
Container queries help most for the components you reuse across different-width slots. A few that come up constantly:
- Cards. The canonical case: a post or product card that stacks its image and text when narrow and sits them side by side when wide, in the same markup.
- Media-and-text blocks. Image beside text on a wide container, image stacked above text on a narrow one, deciding by the block’s own width rather than the page’s.
- Navigation and toolbars. A set of actions that shows labels when there is room and collapses to icons when cramped, based on the toolbar’s container.
- Widgets and sidebars. A widget that adapts its density and layout to whether it sits in a wide footer column or a narrow sidebar.
- Data and stat blocks. Figures that scale their type with
cqiso they stay proportionate whether the block is small or large.
In every case, the win is the same: build the component once, and let it be responsible for looking right wherever it lands. That is the design-system dream that viewport queries could never quite reach.
Why this changes how you build
Container queries are not a small convenience; they change the unit of responsive design from the page to the component. Once a component responds to its own container, you can build a library of genuinely reusable pieces, each of which just works wherever you place it, with no per-placement overrides. Move a card from the main column to the sidebar and it adapts automatically. Drop a widget into a two-column grid and it lays out correctly without knowing it is in a grid. This is what component-based design always promised and media queries could never quite deliver: components that are responsible for their own responsiveness. For anyone building a design system or a block theme with reusable patterns, it removes an entire category of brittle, context-dependent CSS.
Container queries in WordPress block themes
Block themes are exactly where this pays off, because blocks are reusable components placed in wildly different contexts, a group block in a wide content area, a narrow column, a sidebar template part. With media queries, a block that should adapt to its space could only react to the viewport, so the same block looked cramped in a column and fine in the main area. Container queries let a block respond to the actual width it is given, so a query-loop card, a media-and-text block, or a custom pattern can lay itself out correctly wherever an editor drops it. You mark the block’s wrapper as a container and query against it, and the block becomes genuinely portable across your templates, the same component discipline our guide to building a block theme from scratch is built on. This pairs naturally with the token-driven, self-contained approach that makes block themes maintainable, the same thinking behind fixing issues like the dark-mode flash at the component level rather than patching the page.
Gotchas worth knowing
Container queries are straightforward, but a few things trip people up the first time. First, an element cannot query itself, only an ancestor container, so you need a wrapper: mark the wrapper as the container and style the child inside it, not the same element as both. Second, declaring container-type establishes containment, which can affect how the element sizes in some layouts, so if a container collapses unexpectedly, check that its own sizing still makes sense. Third, with nested containers a query targets the nearest matching container, so name your containers when it matters to avoid reacting to the wrong one. And fourth, remember container query units resolve against the container, not the viewport, which is the whole point, but it means cqi behaves differently from vw and you should reach for the right one deliberately. None of these are hard once you know them; they are just the small adjustments of thinking in containers rather than windows.
Browser support and progressive enhancement
Container queries are supported across all current major browsers and have been stable for a while, so in 2026 they are safe for production, and browsers keep extending them, recent releases added the ability to combine multiple container queries in one rule. For older browsers, the graceful path is to treat the container-query styles as an enhancement: write a sensible default layout that works without them, then let the container query improve it where supported. Because an unsupporting browser simply ignores the @container rule and keeps your default, you can adopt container queries without a fallback branch, the base layout holds, and capable browsers get the component-aware version on top.
How do I debug a container query that is not working?
Check three things in order: that an ancestor actually has container-type set, that you are styling a descendant of that container rather than the container element itself, and that a named query is targeting the container name you intended. Browser dev tools show which elements are containers, which makes it quick to confirm the wrapper is set up. Most non-working container queries are a missing container-type or styling the wrong element.
Are container queries good for mobile-first design?
Yes, and they fit it naturally. You write the base, narrow-container styles first, then use @container (min-width: ...) to enhance as space grows, exactly the mobile-first pattern applied at the component level. The difference is that the enhancement triggers on the component’s real space, so a component gets its roomy layout whenever it has room, not only when the whole viewport is wide.
Combining container queries with modern CSS
Container queries are strongest as part of the modern CSS toolkit rather than in isolation. Pair them with clamp() and container units for type and spacing that scale smoothly without breakpoints, so a component is fluid between its query points, not just switching at them. Combine them with CSS grid and flexbox, where the container query decides the grid template and the grid handles the arrangement, a natural division of labor. Use them alongside custom properties so a container query can flip a set of design tokens at once, changing a component’s whole look by adjusting a few variables. And browsers now let you group multiple container conditions in a single rule, so a component can respond to more than one aspect of its container together. The mental model to hold is that container queries answer how much space do I have, and the rest of modern CSS answers what do I do with it, and they compose cleanly. Building components this way, self-scaling with units, self-arranging with grid, self-theming with tokens, and self-responsive with container queries, is what modern component design looks like, and it is dramatically less code than the class-juggling era it replaces.
Frequently asked questions
Do container queries replace media queries entirely?
No, they complement them. Media queries remain the right tool for page-level, viewport-based decisions, like the overall page layout or hiding a navigation on small screens. Container queries are for component-level responsiveness, how a piece adapts to its own space. Most real projects use both: media queries for the page frame, container queries for the components inside it.
What is the difference between container-type inline-size and size?
inline-size makes the container queryable by its width only, which is what you want the vast majority of the time and has fewer side effects. size makes it queryable by both width and height, but it requires the container to have a definite size, which can cause layout issues if misused. Start with inline-size and only reach for size when you genuinely need to query height.
Do container queries hurt performance?
For normal use they are not a performance concern; browsers optimize them well. Marking an element as a container does establish containment, which the browser accounts for, but the cost is negligible for typical component counts. The performance win of removing JavaScript that used to measure elements and toggle classes usually outweighs any tiny cost of the feature itself.
Can I use container query units without writing a container query?
You need an ancestor declared as a container for the units to resolve against, but you can then use units like cqi freely to size things relative to that container, even without an explicit @container block. This is a clean way to make text and spacing scale with a component’s width using just clamp() and container units, no breakpoints required.
Will container queries work inside CSS frameworks like Tailwind?
Yes. Modern versions of popular frameworks include container query support, either natively or via a plugin, so you can use the component-responsive approach within a utility-first workflow. The underlying feature is standard CSS, so it works regardless of how you author your styles, framework or hand-written.
Should I refactor my existing media-query components to container queries?
Refactor opportunistically, not all at once. The best candidates are reusable components that appear in multiple different-width contexts and currently carry placement modifier classes, those get simpler and more reliable with container queries. Page-level layout that genuinely depends on the viewport can stay on media queries. Convert a component when you are already touching it and the payoff is clear, rather than rewriting working code for its own sake.
The bottom line
Media queries made pages responsive to the window; container queries make components responsive to their space, which is what responsive design needed all along. Mark an element as a container with container-type: inline-size, write @container rules and use cqi units, and your components adapt correctly wherever they are placed, no modifier classes, no page-layout assumptions, no brittle overrides. In a block theme especially, this turns blocks into genuinely portable, self-responsive components that work in any template. Container queries are stable, safe to adopt as a progressive enhancement, and once you build with them, going back to viewport-only responsiveness feels like designing with one eye closed. Build components that know their own size, and your layouts stop fighting you.