
Container Queries: Beyond Media Queries
Hit the limits of media queries when building responsive components? Here's how @container solves the problem, broken down with real card component examples.

Hit the limits of media queries when building responsive components? Here's how @container solves the problem, broken down with real card component examples.
Tired of naming classes? Writing CSS directly inside HTML sounds ugly, but it became the world standard. Why?

For visually impaired, for keyboard users, and for your future self. Small `alt` tag makes a big difference.

Class is there, but style is missing? Debugging Tailwind CSS like a detective.

Clicked a button, but the parent DIV triggered too? Events bubble up like water. Understand Propagation and Delegation.

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.
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; }
}
/* 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;
}
}
| 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;
}
/* Works without a name */
.wrapper {
container-type: inline-size;
}
/* Without a name, references the nearest container ancestor */
@container (min-width: 400px) {
.card { display: flex; }
}
<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>
.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.
.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-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);
}
Container Queries don't replace media queries. They solve different problems.
/* 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:
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>
);
}
<div className="@container/sidebar">
<nav className="flex-col @[250px]/sidebar:flex-row">
...
</nav>
</div>
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.
"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 | 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; }
}
}
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:container-type: inline-size@container (min-width: Xpx) syntaxcqw, cqi container-relative unitsTrue component-based development means components that adapt to whatever space they're in, no matter where you put them. Container Queries make that real.