For over a decade, Media Queries have been the cornerstone of responsive design. But they have a fundamental limitation: they only care about viewport size, not the actual container size where a component lives. CSS Container Queries change everything.
The Pain Points of Media Queries
Imagine you have a card component that needs different layouts at different widths:
/* Traditional media query approach */
.card {
display: flex;
flex-direction: column;
}
@media (min-width: 600px) {
.card {
flex-direction: row;
}
}
The problem: that 600px refers to viewport width, not the card container’s width. When the same card component is placed in a sidebar (narrow) versus the main content area (wide), it can’t respond correctly based on actual available space.
┌─────────────────────────────────────────────────────┐
│ Viewport width: 1200px │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ Sidebar (300px) │ │ Main content (900px) │ │
│ │ ┌─────────────────┐ │ │ ┌─────────────────────┐ │ │
│ │ │ Card Component │ │ │ │ Card Component │ │ │
│ │ │ (should be │ │ │ │ (should be │ │ │
│ │ │ vertical) │ │ │ │ horizontal) │ │ │
│ │ │ But media query │ │ │ │ │ │ │
│ │ │ says: viewport │ │ │ │ │ │ │
│ │ │ >600px, so │ │ │ │ │ │ │
│ │ │ horizontal! │ │ │ │ │ │ │
│ │ └─────────────────┘ │ │ └─────────────────────┘ │ │
│ └─────────────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Container Queries to the Rescue
Container Queries let components query their container’s size, not the viewport:
/* Define the container */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Component responds to container size */
.card {
display: flex;
flex-direction: column;
}
@container card (min-width: 400px) {
.card {
flex-direction: row;
}
}
Now, regardless of where the card is placed, it responds correctly based on its actual container width!
Core Syntax Deep Dive
1. Defining Containers
.container {
/* Container type */
container-type: inline-size; /* Query inline axis (width) only */
/* container-type: size; Query both axes (width & height) */
/* container-type: normal; Not a query container */
/* Container name (optional but recommended) */
container-name: sidebar;
/* Shorthand */
container: sidebar / inline-size;
}
2. Container Queries
/* Basic syntax */
@container (min-width: 400px) {
.element { /* styles */ }
}
/* Specifying container name */
@container sidebar (min-width: 300px) {
.element { /* only applies within sidebar container */ }
}
/* Compound conditions */
@container card (min-width: 400px) and (max-width: 800px) {
.element { /* styles */ }
}
/* Supported container query features */
@container (width > 400px) { }
@container (height >= 200px) { }
@container (aspect-ratio > 1/1) { }
@container (orientation: landscape) { }
3. Container Query Units
Container Queries also introduce a new set of relative units:
| Unit | Meaning |
|---|---|
cqw | 1% of container’s width |
cqh | 1% of container’s height |
cqi | 1% of container’s inline size |
cqb | 1% of container’s block size |
cqmin | Smaller of cqi or cqb |
cqmax | Larger of cqi or cqb |
.title {
/* Font size scales with container width */
font-size: clamp(1rem, 4cqi, 2rem);
}
.hero-image {
/* Image height is 50% of container width */
height: 50cqw;
}
Practical Example: Responsive Card Component
Let’s build a truly responsive card component:
<div class="card-grid">
<article class="card-wrapper">
<div class="card">
<img src="thumbnail.jpg" alt="Article thumbnail" class="card-image">
<div class="card-content">
<h2 class="card-title">Article Title</h2>
<p class="card-excerpt">This is the article excerpt describing the main content...</p>
<a href="#" class="card-link">Read more</a>
</div>
</div>
</article>
</div>
/* Container definition */
.card-wrapper {
container: card / inline-size;
}
/* Base styles (small container) */
.card {
display: grid;
gap: 1rem;
padding: 1rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 4px;
}
.card-title {
font-size: clamp(1rem, 3cqi, 1.5rem);
margin: 0;
}
.card-excerpt {
font-size: 0.875rem;
color: #666;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Medium container: horizontal layout */
@container card (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
align-items: start;
}
.card-image {
aspect-ratio: 1;
height: 100%;
}
.card-excerpt {
-webkit-line-clamp: 3;
}
}
/* Large container: more detailed display */
@container card (min-width: 600px) {
.card {
grid-template-columns: 280px 1fr;
padding: 1.5rem;
gap: 1.5rem;
}
.card-image {
aspect-ratio: 4 / 3;
}
.card-excerpt {
-webkit-line-clamp: 4;
font-size: 1rem;
}
.card-link {
margin-top: auto;
}
}
Perfect Pairing with CSS Grid
Container Queries work beautifully with CSS Grid:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Each grid cell automatically becomes a container */
.card-wrapper {
container-type: inline-size;
}
/* Cards adapt based on cell size */
@container (min-width: 350px) {
.card { /* adjust layout */ }
}
This way, when Grid allocates different widths to cells, each card responds based on its actual allocated space.
Style Queries: Querying Custom Properties
Beyond size queries, Container Queries also support Style Queries—querying container’s CSS custom properties:
.card-wrapper {
container-type: inline-size;
--theme: light;
}
.dark-section .card-wrapper {
--theme: dark;
}
/* Style query */
@container style(--theme: dark) {
.card {
background: #1a1a1a;
color: #ffffff;
}
.card-excerpt {
color: #a0a0a0;
}
}
This provides a powerful CSS-only solution for component theming!
Browser Support
As of 2025, Container Queries have full support across all modern browsers:
| Browser | Supported Version |
|---|---|
| Chrome | 105+ |
| Firefox | 110+ |
| Safari | 16+ |
| Edge | 105+ |
For projects requiring older browser support, use progressive enhancement:
/* Base layout (all browsers) */
.card {
display: flex;
flex-direction: column;
}
/* Enhanced layout (browsers supporting Container Queries) */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
}
Best Practices
1. Name Your Containers Explicitly
/* ✅ Good: Use semantic names */
.sidebar { container: sidebar / inline-size; }
.main-content { container: main / inline-size; }
@container sidebar (min-width: 200px) { }
@container main (min-width: 600px) { }
/* ❌ Avoid: Relying on anonymous container lookup */
@container (min-width: 400px) { }
2. Prefer inline-size
/* ✅ Recommended: Query width only */
.container {
container-type: inline-size;
}
/* ⚠️ Use with caution: Querying both axes may impact performance */
.container {
container-type: size;
}
3. Integrate with Component Architecture
/* Component container pattern */
.component-container {
container: component / inline-size;
}
.component-container > .component {
/* Component internal styles */
}
@container component (min-width: 300px) {
.component-container > .component {
/* Responsive variant */
}
}
Summary
CSS Container Queries represent a significant milestone in responsive design evolution:
| Feature | Media Queries | Container Queries |
|---|---|---|
| Query Target | Viewport | Container |
| Component Reusability | Difficult | Easy |
| Context Awareness | None | Full |
| Component Encapsulation | Poor | Excellent |
Key Takeaways:
- Container Queries let components truly “know” their environment
- Combined with container query units (cqw, cqi, etc.), you can achieve fluid typography
- Style Queries provide a CSS-only theming solution
- Works best when combined with CSS Grid
From now on, when building reusable UI components, consider Container Queries first. They not only make code cleaner but give components true “self-adaptive” capabilities.
This technology is changing how we build responsive interfaces. If you haven’t tried it yet, now is the perfect time to start.