For years, the biggest frustration in CSS was that media queries respond to the viewport — not the container a component lives in. A card component that looks perfect in a 3-column grid breaks when you drop it into a narrow sidebar, because the card doesn’t know how much space it actually has. It only knows how wide the entire browser window is.
Container queries fix this. They let components respond to their own container’s size, not the viewport. And as of 2024, they’re supported in every major browser. I’ve been using them in production for over a year now, and they’ve fundamentally changed how I think about responsive design.
Container Queries vs Media Queries
The core difference in one example:
/* Media query: responds to VIEWPORT width */
@media (min-width: 768px) {
.card { display: flex; }
}
/* Container query: responds to CONTAINER width */
@container (min-width: 400px) {
.card { display: flex; }
}
With media queries, your card switches to horizontal layout when the browser window is 768px wide. With container queries, it switches when the space available to the card is 400px wide — whether that’s in a full-width section, a sidebar, a modal, or a dashboard widget.
This is the difference between page-level responsive design and component-level responsive design.
Setting Up Container Queries
Two steps: define a container, then query it.
Step 1: Define a Container
.card-wrapper {
container-type: inline-size;
}
container-type: inline-size tells the browser to track this element’s inline (horizontal) dimension. This is the most common value — you almost always want to respond to width changes.
| Value | What It Tracks |
|---|---|
inline-size | Width only (most common) |
size | Both width and height |
normal | No containment (default) |
Important: container-type: size requires the element to have explicit height. Without it, the browser can’t resolve circular dependencies. I use inline-size for 95% of cases.
Step 2: Query the Container
@container (min-width: 500px) {
.card {
display: flex;
gap: 1.5rem;
}
.card-image {
width: 40%;
}
.card-body {
flex: 1;
}
}
@container (max-width: 499px) {
.card {
display: block;
}
.card-image {
width: 100%;
aspect-ratio: 16 / 9;
}
}
The .card inside .card-wrapper now responds to the wrapper’s width, not the viewport. Drop this card anywhere on your page — it adapts automatically.
Named Containers
When you have nested containers, naming prevents ambiguity:
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: main;
}
/* Query a specific container by name */
@container sidebar (max-width: 300px) {
.nav-item span { display: none; } /* Hide labels, show icons only */
}
@container main (min-width: 800px) {
.article { columns: 2; }
}
You can also use the shorthand:
.sidebar {
container: sidebar / inline-size;
/* equivalent to:
container-name: sidebar;
container-type: inline-size; */
}
Container Query Units
Container queries come with their own set of relative units:
| Unit | Relative To |
|---|---|
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 and cqb |
cqmax | Larger of cqi and cqb |
These are incredibly useful for fluid typography and spacing that scales with the container:
.card-wrapper {
container-type: inline-size;
}
.card-title {
font-size: clamp(1rem, 3cqi, 1.5rem);
/* Scales between 1rem and 1.5rem based on container width */
}
.card-body {
padding: 3cqi;
/* Padding scales proportionally with the container */
}
I use cqi (container query inline) extensively for component-level fluid design. It replaces a lot of the breakpoint-based sizing I used to do.
Real-World Patterns
Pattern 1: Responsive Card Grid
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Each grid cell is a container */
.card-cell {
container-type: inline-size;
}
/* Card adapts to its cell width */
@container (min-width: 350px) {
.product-card {
display: grid;
grid-template-columns: 120px 1fr;
align-items: center;
}
}
@container (max-width: 349px) {
.product-card {
text-align: center;
}
.product-card img {
margin: 0 auto;
}
}
Pattern 2: Dashboard Widget
.widget-container {
container-type: inline-size;
}
/* Compact: icon and value only */
@container (max-width: 200px) {
.widget { padding: 0.75rem; }
.widget-label { display: none; }
.widget-chart { display: none; }
.widget-value { font-size: 1.25rem; }
}
/* Medium: show label */
@container (min-width: 201px) and (max-width: 400px) {
.widget { padding: 1rem; }
.widget-chart { display: none; }
}
/* Full: show everything including chart */
@container (min-width: 401px) {
.widget {
display: grid;
grid-template-columns: 1fr 120px;
padding: 1.5rem;
}
.widget-chart { display: block; }
}
This single widget component works at any size — from a small tile to a full-width panel — without any JavaScript or viewport-based breakpoints.
Pattern 3: Navigation That Adapts
.nav-wrapper {
container: nav / inline-size;
}
/* Full navigation */
@container nav (min-width: 600px) {
.nav { display: flex; gap: 1rem; }
.nav-item { display: flex; align-items: center; gap: 0.5rem; }
.nav-hamburger { display: none; }
}
/* Compact: icons only */
@container nav (min-width: 200px) and (max-width: 599px) {
.nav { display: flex; gap: 0.5rem; }
.nav-label { display: none; }
.nav-hamburger { display: none; }
}
/* Collapsed: hamburger menu */
@container nav (max-width: 199px) {
.nav { display: none; }
.nav-hamburger { display: block; }
}
Browser Support and Fallbacks
Container queries are supported in Chrome 105+, Firefox 110+, Safari 16+, and Edge 105+. That covers approximately 95% of global users as of early 2026.
For the remaining 5%, use a progressive enhancement approach:
/* Default: mobile-first layout (works everywhere) */
.card {
display: block;
}
/* Enhancement: container query for modern browsers */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}
}
The @supports check ensures older browsers get a functional layout — they just don’t get the adaptive behavior.
Common Mistakes
Mistake 1: Forgetting to Set container-type
/* This does NOTHING — no container is defined */
@container (min-width: 500px) {
.card { display: flex; }
}
You must explicitly set container-type on an ancestor element. Container queries don’t implicitly use the parent.
Mistake 2: Querying Height Without Explicit Dimensions
/* Problematic: size containment needs explicit height */
.wrapper {
container-type: size; /* Tracks width AND height */
/* Without explicit height, this creates layout issues */
}
Height-based container queries (container-type: size) require the container to have a defined height. Otherwise, you create a circular dependency: the container’s height depends on its content, but the content’s layout depends on the container’s height.
Mistake 3: Applying container-type to the Queried Element
/* WRONG: you can't query an element's own container */
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { display: flex; } /* This queries the parent, not .card itself */
}
The container-type must be on an ancestor of the element you’re styling inside @container. The element with container-type is the container; the elements inside it are what gets styled.
Optimize your CSS: Once you’ve built your container query styles, use our CSS Minifier to compress the production bundle. And for the color values in your responsive components, our Color Converter helps maintain consistency across your design system.
Container Queries + CSS Grid: The Perfect Pair
Container queries and CSS Grid complement each other beautifully:
/* Grid handles the macro layout */
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
/* Each grid item is a container */
.dashboard > * {
container-type: inline-size;
}
/* Container queries handle the micro layout */
@container (min-width: 400px) {
.stat-card {
display: flex;
justify-content: space-between;
}
}
Grid decides how many columns there are and how wide each cell is. Container queries inside each cell decide how the component within that cell renders. This separation of concerns makes your CSS dramatically more maintainable.
Further Reading
Building responsive CSS? Use our CSS Gradient Generator for container-aware backgrounds, Box Shadow Generator for adaptive elevation, and CSS Minifier for production-ready output.