CSS Container Queries Finally Fixed Responsive Design. Here's How to Use Them.
Last week I rebuilt a dashboard component for the third time. It was a simple stats card — icon on the left, number on the right. Looked perfect in the sidebar. Then I dropped it into the main content area and it stretched out like a melted candy bar. I sighed, reached for a media query, and stopped myself.
I'd been fighting viewport-based breakpoints for years. Every time I reused a component in a different layout context, I had to override its styles. Sometimes with ugly !important flags. Sometimes with a new wrapper class. Always with the feeling that there had to be a better way.
Turns out, there is. It's called container queries, and they've been shipping in every major browser since 2023. If you haven't touched them yet — or you tried them early and found them flaky — you're in for a treat. They're stable, they're powerful, and they'll fundamentally change how you write responsive CSS.
What's Wrong With Media Queries?
Nothing, for page-level layouts. If you're building a traditional responsive page where the header, sidebar, and main content area each respond to the browser viewport, media queries work fine.
The problem starts when you build reusable components.
Think about a product card. On a wide desktop screen, it shows a horizontal layout with the image on the left and details on the right. On a phone, it stacks vertically. Clean, right? But what happens when you put that same card inside a narrow sidebar widget, or a three-column grid on a widescreen monitor?
/* The old way — guesswork and overrides */
.product-card {
display: flex;
gap: 1rem;
}
/* What breakpoint do you even use here? */
@media (max-width: 768px) {
.product-card {
flex-direction: column;
}
}
/* Now inside a sidebar — oops, it's already stacked */
.sidebar .product-card {
flex-direction: column; /* Had to override */
}
That 768px breakpoint is completely arbitrary. It has nothing to do with the card itself — it's just the point where the page happens to narrow. The card doesn't care about the viewport. It cares about how much space it has to work with.
Container Queries — The Mental Shift
Container queries flip the model: instead of asking "how wide is the browser?", you ask "how wide is my parent container?"
The syntax is refreshingly direct. First, you designate an element as a container:
/* Any block-level element can be a container */
.sidebar-panel {
container-type: inline-size;
/* or shorthand: contain: layout inline-size style; */
}
Then you write queries against that container:
@container (min-width: 400px) {
.product-card {
flex-direction: row;
}
}
@container (max-width: 399px) {
.product-card {
flex-direction: column;
}
}
That's it. The @container rule checks the container's inline-size (logical width), not the viewport width. Drop the card anywhere — sidebar, main content, a modal — and it adapts to whatever space it actually has.
Setting Up Containers the Right Way
You have a few options for container-type:
inline-size— Query based on the container's width (most common).size— Query based on both width and height. Use sparingly — it forces the browser to do extra layout work.normal— The element participates in containment but doesn't enable size queries.
You can also name your containers, which becomes essential once you have nested containers:
.card-grid {
container-name: cards;
container-type: inline-size;
}
@container cards (min-width: 600px) {
/* styles only for containers named "cards" */
}
Shorthand for the win:
.card-grid {
container: cards / inline-size;
}
Real Example: A Component That Actually Adapts
Here's the dashboard stat card I mentioned earlier. I rebuilt it with container queries and never touched it again:
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-body">
<span class="stat-value">$12,450</span>
<span class="stat-label">Revenue</span>
</div>
</div>
</div>
.stats-grid {
container: stats / inline-size;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
}
.stat-card {
display: flex;
gap: 0.75rem;
padding: 1rem;
border-radius: 0.75rem;
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* When each card has at least 300px of space */
@container stats (min-width: 300px) {
.stat-card {
flex-direction: row;
align-items: center;
}
.stat-icon {
font-size: 2rem;
}
}
/* Narrower than 300px — stack vertically */
@container stats (max-width: 299px) {
.stat-card {
flex-direction: column;
text-align: center;
}
.stat-icon {
font-size: 1.5rem;
margin-bottom: 0.25rem;
}
}
This card adapts based on its actual available width inside the grid. If the grid column shrinks to 200px, the card stacks. If it gets 350px, it goes horizontal. No media query involved.
The Gotchas Nobody Warns You About
Container queries aren't magic. Here's what tripped me up:
1. Containment affects layout
When you set container-type: inline-size, the element becomes a containment context. Its children can't overflow in the inline direction. If you have absolute-positioned elements inside, they'll clip unless you account for it.
2. Nested containers need names
If you nest containers, a @container query without a name will match the nearest ancestor container. That might not be what you want. Always name your containers once you have more than one level:
.page-section {
container: section / inline-size;
}
.card-grid {
container: grid / inline-size;
}
/* Target only the grid container */
@container grid (min-width: 400px) { ... }
3. Legacy browser support
Container queries are supported in Chrome 105+, Firefox 110+, Safari 16.4+, and Edge 105+. That covers roughly 95% of global users as of 2026. If you need to support the remaining 5%, provide a sensible fallback using media queries — the container query styles just won't apply, but the base styles still work.
4. No container-type on the <html> or <body>
You can't set container queries on the root elements. If you need a page-wide container, wrap everything in a <div>.
Where Container Queries Shine
After a year of using them in production, here's where I reach for container queries every time:
- Dashboard widgets — Cards, charts, and stats that move between layouts.
- Design system components — Buttons, form fields, and modals that need to work anywhere.
- Email templates — Yes, email. Containers work in most modern email clients now.
- CMS-driven pages — When editors can drop components anywhere and you can't control the context.
- Third-party embeds — Code that runs inside other people's layouts (widgets, chat, analytics).
The common thread? Components that you don't control the parent of.
Container Query Units
One feature that doesn't get enough attention: container query units. Just like viewport units (vw, vh), you get units relative to your container:
cqw— 1% of container's widthcqh— 1% of container's heightcqi— 1% of container's inline sizecqb— 1% of container's block sizecqmin/cqmax— the smaller/larger ofcqiandcqb
These are fantastic for fluid typography inside components:
.stat-value {
font-size: clamp(1.25rem, 4cqi + 0.5rem, 3rem);
}
That heading grows and shrinks with its container, not the viewport. Drop it in a sidebar, it's small. Move it to the hero area, it scales up. No media query needed.
Should You Use Them Today?
Yes. Absolutely yes. Container queries aren't experimental — they're a W3C Candidate Recommendation and they ship in every browser your users actually use. The only reason not to use them is if you're supporting IE11 (and if you are, I'm so sorry).
Start small. Pick one reusable component — a card, a sidebar widget, a form field — and convert its media queries to container queries. Once you feel the difference, you'll find yourself reaching for @container before @media every single time.
I rebuilt that dashboard component in about 10 minutes. It's been deployed for six months and I haven't touched the CSS once. That's the real win — CSS that doesn't break when you move things around.
Got a component that's been driving you crazy with media query overrides? Try container queries and see how much cleaner your CSS gets. If you're looking for a place to organize all those component snippets, Snippet Ark lets you save, tag, and retrieve patterns exactly when you need them — no more digging through old projects for that one perfect card layout.