CSS Container Queries: The Future of Responsive Design
For years, responsive web design meant one thing: viewport-based media queries. We'd write @media (max-width: 768px) and call it a day. But this approach has a fundamental flaw — a component doesn't know where it lives. A sidebar card that looks fine at 320px viewport width might be crammed inside a 200px container on a wide screen. CSS Container Queries change everything by letting components respond to their parent container's size rather than the browser viewport, enabling truly reusable, context-aware components.
Container Queries solve the classic design system problem: the same card component should look different when placed in a narrow sidebar versus a wide main column — without JavaScript, without wrapper hacks, and without writing duplicate media queries for every possible layout context.
@container vs @media: The Core Difference
To understand why container queries are such a big deal, you first need to feel the pain of viewport-only media queries in component-driven development.
Imagine a product card component used in three different layouts on the same page: a full-width hero section, a 3-column grid, and a narrow sidebar widget. With @media, you're always reacting to the viewport, not to where the card actually lives. You end up with brittle CSS that breaks whenever your layout changes.
Media queries react to the viewport; container queries react to the parent container
Syntax and Setup
Container queries have a two-part syntax. First, you declare an element as a containment context using the container-type property. Then, you write rules that apply based on that container's size using the @container at-rule.
Step 1: Declare a Container
Set container-type on the parent
Apply container-type: inline-size to the element whose width you want child components to respond to. Use size to respond to both width and height.
/* The wrapper becomes a containment context */
.card-wrapper {
container-type: inline-size;
/* Optional: give it a name for targeted queries */
container-name: card;
/* Shorthand: name / type */
container: card / inline-size;
}
Step 2: Write @container Rules
Write @container rules for child elements
Inside @container, select child elements and override their styles based on the container's dimensions, just like media queries but scoped to the container.
/* Default styles: stacked layout */
.card {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
}
.card__image {
width: 100%;
aspect-ratio: 16/9;
border-radius: 8px;
object-fit: cover;
}
/* When the container is at least 400px wide: side-by-side layout */
@container (min-width: 400px) {
.card {
flex-direction: row;
align-items: center;
}
.card__image {
width: 160px;
flex-shrink: 0;
aspect-ratio: 1;
}
}
/* When the container is at least 600px wide: larger text + extra info */
@container (min-width: 600px) {
.card__title {
font-size: 1.4rem;
}
.card__extra-info {
display: block; /* Hidden by default */
}
}
Named Containers for Precision
When you have nested containers or multiple containers on a page, named containers let you target the right ancestor. Without a name, @container matches the nearest container ancestor.
.sidebar {
container: sidebar / inline-size;
}
.main-content {
container: main / inline-size;
}
/* Only triggers when the .sidebar container is narrow */
@container sidebar (max-width: 250px) {
.widget {
font-size: 0.85rem;
padding: 8px;
}
}
/* Only triggers when the .main-content container is wide */
@container main (min-width: 700px) {
.article-card {
display: grid;
grid-template-columns: 200px 1fr;
}
}
A container cannot respond to its own size — only its descendants can. If you apply container-type to .card, you cannot use @container rules to style .card itself — only its children. This is by design to prevent circular dependency loops in layout calculations.
Container Query Units
Along with the @container rule, CSS introduced a new set of length units that are relative to the queried container — similar to how vw and vh are relative to the viewport.
| Unit | Meaning | Equivalent |
|---|---|---|
cqw |
1% of container's width | Like vw but for container |
cqh |
1% of container's height | Like vh but for container |
cqi |
1% of container's inline size | Width in horizontal writing modes |
cqb |
1% of container's block size | Height in horizontal writing modes |
cqmin |
Smaller of cqi and cqb |
Like vmin |
cqmax |
Larger of cqi and cqb |
Like vmax |
Container query units are especially useful for fluid typography and spacing that scales with the component rather than the screen:
.widget-wrapper {
container-type: inline-size;
}
.widget-title {
/* Font size scales from ~14px (tiny container) to ~28px (wide container) */
font-size: clamp(0.875rem, 4cqi, 1.75rem);
}
.widget-icon {
/* Icon stays proportional to container */
width: 10cqi;
height: 10cqi;
max-width: 48px;
max-height: 48px;
}
.widget-padding {
/* Padding scales with available space */
padding: 2cqi 3cqi;
}
Real-World Use Cases
Theory is great — but container queries really shine in practical component-driven scenarios. Let's look at three patterns you can apply immediately.
1. The Adaptive Card Component
This is the canonical use case: a card that stacks vertically in a narrow grid column and switches to a horizontal layout when placed in a wider area.
<!-- In a narrow sidebar -->
<aside class="sidebar">
<div class="card-container">
<article class="product-card">
<img class="product-card__img" src="shoe.jpg" alt="Running Shoe">
<div class="product-card__body">
<h3>Air Runner Pro</h3>
<p class="product-card__desc">Lightweight trail shoe for serious runners.</p>
<span class="product-card__price">$129</span>
</div>
</article>
</div>
</aside>
<!-- Exact same markup in main content -->
<main class="main-grid">
<div class="card-container">
<article class="product-card">
<!-- Same structure -->
</article>
</div>
</main>
.card-container {
container-type: inline-size;
}
/* Base: stacked, compact */
.product-card {
display: flex;
flex-direction: column;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.product-card__img {
width: 100%;
aspect-ratio: 4/3;
object-fit: cover;
}
.product-card__body {
padding: 12px;
}
.product-card__desc {
display: none; /* Hidden when too narrow */
}
/* 280px+: show description */
@container (min-width: 280px) {
.product-card__desc {
display: block;
font-size: 0.85rem;
color: #718096;
}
}
/* 380px+: go horizontal */
@container (min-width: 380px) {
.product-card {
flex-direction: row;
align-items: stretch;
}
.product-card__img {
width: 140px;
aspect-ratio: 1;
flex-shrink: 0;
}
.product-card__body {
padding: 16px;
display: flex;
flex-direction: column;
justify-content: center;
}
}
/* 560px+: full-featured hero card */
@container (min-width: 560px) {
.product-card__img {
width: 220px;
}
.product-card__price {
font-size: 1.4rem;
font-weight: 700;
}
}
The same .product-card component now automatically adapts:
- Under 280px container: Compact image-only card with title and price
- 280–379px container: Adds description text below image
- 380–559px container: Side-by-side image and text layout
- 560px+ container: Spacious hero card with larger typography
No JavaScript. No layout-specific CSS classes. The component is truly self-contained.
2. Navigation That Adapts to Its Container
Navigation menus are another great candidate. A nav inside a full-width header can show all links; the same nav dropped into a sidebar should collapse into a compact list.
.nav-wrapper {
container-type: inline-size;
}
/* Default: vertical stack */
.nav-list {
display: flex;
flex-direction: column;
gap: 4px;
list-style: none;
padding: 0;
}
.nav-item a {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 6px;
text-decoration: none;
color: inherit;
}
/* 240px+: show icon + label */
@container (min-width: 240px) {
.nav-item .nav-label {
display: inline;
}
}
/* 480px+: horizontal tabs */
@container (min-width: 480px) {
.nav-list {
flex-direction: row;
gap: 8px;
}
.nav-item a {
padding: 10px 16px;
}
}
3. Data Tables That Reflow
Wide tables that get squeezed into narrow panels are a UX nightmare. Container queries let you reflow table rows into cards when the container is too narrow to show all columns.
.table-wrapper {
container-type: inline-size;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
/* Below 500px: reflow to card layout */
@container (max-width: 500px) {
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
/* Hide column headers — labels come from data-label */
position: absolute;
top: -9999px;
left: -9999px;
}
tr {
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-bottom: 12px;
padding: 8px;
}
td {
border: none;
padding: 6px 8px;
display: flex;
justify-content: space-between;
}
td::before {
content: attr(data-label);
font-weight: 600;
color: #718096;
}
}
Browser Support and Progressive Enhancement
Container queries landed in all major browsers in late 2022 and are now considered baseline-supported. You can use them in production without hesitation for most projects.
| Browser | @container Support Since | CQ Units Support Since | Global Usage |
|---|---|---|---|
| Chrome / Edge | 105 (Aug 2022) | 105 (Aug 2022) | ~72% |
| Firefox | 110 (Feb 2023) | 110 (Feb 2023) | ~4% |
| Safari | 16 (Sep 2022) | 16 (Sep 2022) | ~19% |
| IE 11 | Not supported | Not supported | <0.5% |
Progressive Enhancement Pattern
Write your default styles first (no container queries needed), then enhance with container queries. Browsers that don't support them will simply display the default layout — perfectly acceptable as a fallback.
/* Base styles work everywhere */
.card {
display: flex;
flex-direction: column;
padding: 16px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* Optional: wrap in @supports for extra safety */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
}
/* Can also use feature detection in JS */
if (CSS.supports('container-type', 'inline-size')) {
document.body.classList.add('supports-cq');
}
Unlike many CSS features, container queries don't have a reliable polyfill for production use. The container-query-polyfill package from Google Chrome Labs works in development but adds JavaScript overhead. For production, lean on progressive enhancement — the baseline fallback is almost always acceptable.
Container Queries vs Other Responsive Techniques
Comparing responsive design techniques for component-level adaptation
The right choice depends on your context. Use media queries for page-level layout decisions (header height, sidebar visibility, overall column count). Use container queries for component-level adaptation (how a card, widget, or nav item renders in its allocated space). The two techniques are complementary, not competing.
Advanced Patterns and Tips
Style Queries (Experimental)
Container queries are evolving. Style queries — currently in Chrome and behind a flag in Firefox — let you query computed style values rather than dimensions. This enables theming patterns where child components adapt to a parent's custom property values:
/* Style query: respond to a CSS custom property on the container */
.card-wrapper {
container-type: style;
--card-variant: featured;
}
/* Children can query parent's custom property value */
@container style(--card-variant: featured) {
.card {
background: linear-gradient(135deg, #7c3aed, #4f46e5);
color: white;
transform: scale(1.02);
box-shadow: 0 10px 40px rgba(124, 58, 237, 0.4);
}
}
Combining with CSS Grid and Flexbox
Container queries work best when paired with intrinsic sizing. Use CSS Grid's auto-fill and minmax() to create a fluid grid, then let container queries control how each cell's content renders:
/* Grid adapts to viewport/parent */
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
}
/* Each cell is a container */
.product-grid > * {
container-type: inline-size;
}
/* Card content adapts to cell width */
@container (min-width: 300px) {
.product-card {
/* wider card layout */
}
}
/* For large containers (e.g. featured product slot) */
@container (min-width: 500px) {
.product-card {
/* hero layout */
}
}
Container Query Quick-Reference Cheat Sheet
| Feature | Syntax | Notes |
|---|---|---|
| Declare container (inline) | container-type: inline-size |
Responds to width only |
| Declare container (both axes) | container-type: size |
Responds to width & height |
| Name a container | container-name: sidebar |
Enables targeted queries |
| Shorthand | container: sidebar / inline-size |
name / type |
| Min-width query | @container (min-width: 400px) |
Mobile-first approach |
| Max-width query | @container (max-width: 400px) |
Desktop-first approach |
| Named query | @container sidebar (min-width: 200px) |
Targets a specific container |
| CQ unit | font-size: 4cqi |
4% of container's inline size |
| Feature detection | @supports (container-type: inline-size) |
Safe progressive enhancement |
Conclusion and Key Takeaways
CSS Container Queries represent the most significant shift in responsive design thinking since media queries were introduced in CSS 2.1. They allow you to build components that are truly self-contained — components that know how to adapt to their context without any outside coordination.
Key Takeaways
- Declare with
container-type: Setcontainer-type: inline-sizeon wrapper elements to create a containment context - Query with
@container: Descendants can respond to the nearest (or named) container's dimensions - Use container units (
cqi,cqw): Size elements proportionally to their container for fluid component scaling - Pair with @media, not replace it: Use media queries for page-level layout, container queries for component-level adaptation
- Progressive enhancement: Default styles work without container query support — they're a layer of enhancement, not a dependency
- Browser support is excellent: All major browsers have supported container queries since early 2023 — ship with confidence
"Container queries allow us to stop thinking about 'how wide is the screen?' and start thinking 'how much space does this component have?' — a fundamentally more reusable way to design."
— Una Kravets, CSS Working Group
Start small: pick one component in your current project — a card, a widget, a navigation element — and refactor it to use container queries. You'll immediately feel how much cleaner the mental model is compared to viewport media queries. Once you make the switch, you won't look back.