Container Queries: Beyond Media Queries
Prologue: The Uncomfortable Truth About Media Queries
At some point in responsive web development, you hit a wall.
You build a ProductCard component. In the sidebar it should stack vertically. In the main content area it should go horizontal. Dashboard: 3 columns. Mobile: 1 column.
Trying to solve this with media queries:
@media (max-width: 768px) {
.product-card { flex-direction: column; }
}
@media (min-width: 769px) and (max-width: 1024px) {
.sidebar .product-card { flex-direction: column; }
.main-content .product-card { flex-direction: row; }
}
/* But what about when the sidebar is open/closed...? */
Media queries know the viewport width. They don't know how much space the component actually has. That's the fundamental limitation.
Container Queries were built to fix this.
The Core Idea: Querying the Parent, Not the Viewport
An Analogy
A media query is like deciding how to furnish a room based on the total square footage of the building. What matters is the room itself, not the whole building.
Container Queries measure each room directly and decide its interior accordingly.
/* Media query: looks at the viewport (the whole building) */
@media (min-width: 768px) {
.card { display: flex; }
}
/* Container Query: looks at the parent container (the room) */
@container card-wrapper (min-width: 400px) {
.card { display: flex; }
}
@container Syntax: Full Breakdown
Basic Structure
/* Step 1: Define the container */
.card-wrapper {
container-type: inline-size;
container-name: card-wrapper;
/* Shorthand */
container: card-wrapper / inline-size;
}
/* Step 2: Write the query */
@container card-wrapper (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}
container-type Options
| Value | Meaning | Queryable Axis |
|---|---|---|
inline-size | Inline direction (usually horizontal) | Width |
size | Both width and height | Width + height |
normal | Default, not queryable | None |
Use inline-size for the vast majority of cases. size only when you also need height queries.
/* Most common */
.container {
container-type: inline-size;
}
/* Named (recommended) */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* Shorthand */
.card-list {
container: card-list / inline-size;
}
Anonymous Containers
/* Works without a name */
.wrapper {
container-type: inline-size;
}
/* Without a name, references the nearest container ancestor */
@container (min-width: 400px) {
.card { display: flex; }
}
Real World: Card Component
The Problem
<main>
<div class="main-grid">
<div class="card-wrapper">
<article class="product-card">...</article>
</div>
</div>
</main>
<aside>
<div class="sidebar">
<div class="card-wrapper">
<article class="product-card">...</article>
</div>
</div>
</aside>
Solved with Container Queries
.card-wrapper {
container-type: inline-size;
container-name: product-card;
}
/* Default: narrow space, vertical layout */
.product-card {
display: grid;
grid-template-rows: auto 1fr auto;
border-radius: 12px;
overflow: hidden;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.product-card__image {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
}
.product-card__description {
display: none; /* Hidden at small sizes */
}
/* 400px+: horizontal layout */
@container product-card (min-width: 400px) {
.product-card {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
}
.product-card__image {
grid-row: 1 / 3;
aspect-ratio: 3/4;
height: 100%;
}
}
/* 600px+: richer layout with description */
@container product-card (min-width: 600px) {
.product-card {
grid-template-columns: 300px 1fr;
}
.product-card__description {
display: block; /* Now visible */
color: #666;
}
}
The same card stacks vertically in the sidebar, goes horizontal in the main grid, and shows a description when there's plenty of room — entirely based on the container's width, not the viewport.
container-name in Nested Containers
.page-layout {
container: page / inline-size;
}
.sidebar {
container: sidebar / inline-size;
}
.widget {
container: widget / inline-size;
}
/* Reference specific containers by name */
@container page (min-width: 1200px) {
.sidebar { width: 300px; }
}
@container sidebar (max-width: 250px) {
.widget { padding: 0.5rem; }
}
@container widget (min-width: 200px) {
.widget-content { display: flex; }
}
Container Query Units: cqw, cqh, cqi, cqb
Container-relative units — the vw/vh of the container world.
| Unit | Meaning |
|---|---|
cqw | 1% of container width |
cqh | 1% of container height |
cqi | 1% of container inline size |
cqb | 1% of container block size |
cqmin | Smaller of cqi and cqb |
cqmax | Larger of cqi and cqb |
/* Font size scales with container width */
.card-title {
font-size: clamp(1rem, 4cqw, 2rem);
}
/* Padding proportional to container */
.card-padding {
padding: 2cqi;
}
The clamp() + cqw combination is especially powerful for fluid typography:
.hero-title {
font-size: clamp(1.5rem, 6cqw, 3rem);
}
Combining with Media Queries
Container Queries don't replace media queries. They solve different problems.
Division of Responsibility
/* Media query: overall page structure */
@media (min-width: 1024px) {
.page-layout {
display: grid;
grid-template-columns: 280px 1fr;
}
}
/* Container query: individual component internals */
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { flex-direction: row; }
}
Media queries are great for:
- Overall page layout changes
- Navigation pattern changes (hamburger menu, etc.)
- Global typography scaling
- Background colors, color themes
Container queries are great for:
- Internal layout of reusable components
- Widgets, cards, panels — isolated UI units
- Same component used in different containers (sidebar vs. main)
React + Tailwind Integration
Tailwind CSS v3.2+
npm install @tailwindcss/container-queries
// tailwind.config.js
module.exports = {
plugins: [
require('@tailwindcss/container-queries'),
],
}
function ProductCard({ product }) {
return (
<div className="@container">
<article className="flex flex-col @[400px]:flex-row gap-4 p-4 bg-white rounded-xl shadow">
<img
src={product.image}
className="w-full @[400px]:w-48 aspect-video @[400px]:aspect-square object-cover rounded-lg"
alt={product.name}
/>
<div className="flex-1">
<h3 className="text-base @[400px]:text-lg @[600px]:text-xl font-semibold">
{product.name}
</h3>
<p className="hidden @[600px]:block text-gray-600 mt-1">
{product.description}
</p>
</div>
</article>
</div>
);
}
Named Containers in Tailwind
<div className="@container/sidebar">
<nav className="flex-col @[250px]/sidebar:flex-row">
...
</nav>
</div>
Style Queries (Experimental)
Container Queries are also expanding to style queries — querying based on CSS custom property values.
.card-wrapper {
container-type: style;
--variant: featured;
}
@container style(--variant: featured) {
.card {
border: 2px solid gold;
background: #fffbe6;
}
}
function Card({ variant = 'default' }) {
return (
<div
className="card-wrapper"
style={{ '--variant': variant } as React.CSSProperties}
>
<article className="card">...</article>
</div>
);
}
CSS custom properties become a bridge between JavaScript state and CSS behavior — handle variant styling entirely in CSS.
Performance Considerations
"Won't layout calculation get more expensive?"
Browsers implement Container Queries efficiently. Style recalculation only happens for children when the container's size changes. If the container stays the same size, no recalculation.
One thing to avoid:
/* Don't do this — querying container, then changing it */
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
width: 600px; /* Modifying the container itself → potential loop */
}
}
/* Safe: only modify children */
@container (min-width: 400px) {
.card > .card-content {
display: flex; /* OK */
}
}
Never change the container's own size inside a container query. Only modify children.
Browser Support (March 2026)
| Browser | Size Queries | Style Queries |
|---|---|---|
| Chrome 105+ | Supported | Experimental |
| Edge 105+ | Supported | Experimental |
| Safari 16+ | Supported | Partial |
| Firefox 110+ | Supported | Planned |
Global support rate: ~90%+. Production-ready right now.
If you need a fallback:
/* Base styles for non-supporting browsers */
.card { display: flex; flex-direction: column; }
/* Progressive enhancement */
@supports (container-type: inline-size) {
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { flex-direction: row; }
}
}
Summary
Container Queries are one of the most long-awaited CSS features in history. If media queries are the tool for page layout, container queries are the tool for component layout.
Key takeaways:
- Declare containers with
container-type: inline-size - Query with
@container (min-width: Xpx)syntax - Use
cqw,cqicontainer-relative units - Not a replacement for media queries — they're complementary
- 90%+ browser support, use it in production today
True component-based development means components that adapt to whatever space they're in, no matter where you put them. Container Queries make that real.