CSS Container Queries
Container queries let a component adapt its layout based on the size of its parent container — not the browser window. This is the biggest shift in responsive design since media queries. A card that looks different at 300px wide and 600px wide, regardless of what screen the user is on, is the core use case.
The Problem with Media Queries
DIAGRAM — Why media queries fall short for components:
A card component used in two places:
Sidebar (300px wide): Main content (700px wide):
┌──────────────┐ ┌──────────────────────────────┐
│Card (stacked)│ │ Card (horizontal layout) │
└──────────────┘ └──────────────────────────────┘
@media (min-width: 768px) doesn't help here.
Both cards are on the same 1200px-wide screen.
The screen width is the same — but the card's container is different.
Media queries watch the screen. Container queries watch the container.
How Container Queries Work
Two steps are required:
- Declare the parent as a containment context using
container-type - Write a
@containerrule that fires when the container reaches a certain size
Step 1 — Declare a Containment Context
/* The parent that the component lives inside */
.card-wrapper {
container-type: inline-size;
/* "inline-size" means: track the horizontal (width) size */
}
container-type values:
inline-size— tracks width (most common, works for most layouts)size— tracks both width and heightnormal— no size tracking (default), but can still be queried for style
Step 2 — Write a @container Rule
/* Base card style (small — stacked layout) */
.card {
display: flex;
flex-direction: column;
}
/* When the container reaches 500px or more, go horizontal */
@container (min-width: 500px) {
.card {
flex-direction: row;
}
}
DIAGRAM — @container response:
.card-wrapper at 300px wide:
┌──────────────────┐
│ [Image] │ ← stacked: flex-direction: column
│ [Title] │
│ [Description] │
│ [Button] │
└──────────────────┘
.card-wrapper at 600px wide:
┌───────────────────────────────────────┐
│ [Image] │ [Title] │ ← horizontal: row
│ │ [Description] │
│ │ [Button] │
└───────────────────────────────────────┘
Named Containers
When you have multiple nested containers, name them so @container rules target the right one.
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
/* Only fires when the sidebar container is narrow */
@container sidebar (max-width: 250px) {
.nav-item span {
display: none; /* hide label, show icon only */
}
}
/* Only fires when the main content container is wide */
@container main (min-width: 700px) {
.article-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Shorthand for container-type + container-name */
.sidebar {
container: sidebar / inline-size;
}
Container Query Units
Inside a @container block, you can use special units relative to the container size.
| Unit | Meaning |
|---|---|
| cqw | 1% of the container's width |
| cqh | 1% of the container's height |
| cqi | 1% of the container's inline size (usually width) |
| cqb | 1% of the container's block size (usually height) |
| cqmin | 1% of the smaller of cqi or cqb |
| cqmax | 1% of the larger of cqi or cqb |
/* Font size scales with the container, not the viewport */
@container (min-width: 300px) {
.card-title {
font-size: 4cqw; /* 4% of the container width */
}
}
Practical Full Example — Reusable Card
<!-- Used in a narrow sidebar -->
<aside class="sidebar">
<div class="card">...</div>
</aside>
<!-- Used in a wide main area -->
<main class="content-area">
<div class="card">...</div>
</main>
/* Both containers declared */
.sidebar, .content-area {
container-type: inline-size;
}
/* Card base — mobile-like stacked layout */
.card {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
background: white;
border-radius: 8px;
}
.card img {
width: 100%;
height: 180px;
object-fit: cover;
border-radius: 6px;
}
.card h2 { font-size: 1rem; }
/* Card responds to its container — not the screen */
@container (min-width: 480px) {
.card {
flex-direction: row;
align-items: center;
}
.card img {
width: 200px;
height: 140px;
flex-shrink: 0;
}
.card h2 { font-size: 1.25rem; }
}
@container (min-width: 700px) {
.card {
padding: 24px;
gap: 20px;
}
.card h2 { font-size: 1.5rem; }
}
DIAGRAM — Same card, different containers on the same screen:
Sidebar (300px): Main content (800px):
┌──────────────┐ ┌────────────────────────────────────┐
│ [Card image] │ │ [Image] │ Card Title (bigger text) │
│ Card title │ │ │ Description │
│ Description │ │ │ [Button] │
│ [Button] │ └────────────────────────────────────┘
└──────────────┘
One component. Two containers. Two different layouts.
Zero JavaScript.
Container Queries vs Media Queries
| Feature | Media Queries | Container Queries |
|---|---|---|
| Watches | Viewport (browser window) | Parent container size |
| Best for | Page-level layout breakpoints | Component-level adaptation |
| Reusable components | Limited — breaks when moved | Excellent — adapts anywhere |
| Browser support | All browsers | All modern browsers (2023+) |
When to Use Container Queries vs Media Queries
DIAGRAM — Decision guide:
"Is this about the whole page layout?"
YES → Use @media
Example: switch from sidebar layout to mobile stack at 768px
"Is this about a reusable component?"
YES → Use @container
Example: card switches from stacked to side-by-side based on its parent
Container queries are the future of component-based CSS. They let you build truly reusable components that adapt to any context they're placed in — no JavaScript, no wrapper-specific overrides, just clean CSS that responds to its environment.
