Container Queries: Components That Adapt to Their Container
Build responsive components with CSS container queries that adapt to their container instead of the viewport, so one layout works in sidebars and modals.

Media queries answer the wrong question for component-based design. When you write @media (min-width: 768px), you're asking "how wide is the viewport?" But what your component actually needs to know is "how much space do I have?" A card component in a narrow sidebar and the same card in a wide content area share the same viewport width but need completely different layouts.
Container queries fix this by letting components respond to their container's dimensions instead of the viewport. This makes components truly portable—drop them into any layout context and they adapt automatically. It's the responsive design primitive we've wanted since we started building component libraries.
Container Queries Fundamentals
To use container queries, you define a containment context on the parent element, then write queries against that container from the child.
/* ❌ Media queries: component responds to viewport */
.card {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 600px) {
.card {
grid-template-columns: 200px 1fr;
}
}
/* This card always switches at 600px viewport width
even if it's in a 300px sidebar */
/* ✅ Container queries: component responds to its container */
.card-container {
container-type: inline-size;
container-name: card;
}
.card {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
padding: 1rem;
}
@container card (min-width: 400px) {
.card {
grid-template-columns: 200px 1fr;
}
}
@container card (min-width: 700px) {
.card {
grid-template-columns: 250px 1fr;
gap: 2rem;
padding: 2rem;
}
}<!-- Same component, different contexts, different layouts -->
<!-- In sidebar: stays vertical (container < 400px) -->
<aside class="sidebar">
<div class="card-container">
<article class="card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="card-content">
<h3>Article Title</h3>
<p>Description text here...</p>
</div>
</article>
</div>
</aside>
<!-- In main content: goes horizontal (container > 400px) -->
<main class="content">
<div class="card-container">
<article class="card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="card-content">
<h3>Article Title</h3>
<p>Description text here...</p>
</div>
</article>
</div>
</main>Container Query Units
Container queries introduce new units relative to the container's dimensions, replacing viewport units for component-internal sizing.
.card-container {
container-type: inline-size;
container-name: card;
}
/* Container query units */
.card-title {
/* cqi = 1% of container's inline size */
font-size: clamp(1rem, 3cqi, 2rem);
}
.card-image {
/* 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 */
height: 30cqi;
object-fit: cover;
}
/* Fluid spacing based on container */
.card {
padding: clamp(0.75rem, 3cqi, 2rem);
gap: clamp(0.5rem, 2cqi, 1.5rem);
}Real-World Component Patterns
Here are practical patterns that demonstrate where container queries solve problems that media queries can't.
/* Navigation that adapts to its container */
.nav-container {
container-type: inline-size;
container-name: nav;
}
.nav-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
list-style: none;
padding: 0;
}
.nav-label {
display: none;
}
.nav-icon {
width: 24px;
height: 24px;
}
/* When nav has enough space, show labels */
@container nav (min-width: 200px) {
.nav-list {
gap: 0.5rem;
}
.nav-label {
display: inline;
}
}
/* When nav has lots of space, go horizontal */
@container nav (min-width: 600px) {
.nav-list {
flex-direction: row;
gap: 1rem;
}
}/* Data table that adapts its columns to available space */
.table-container {
container-type: inline-size;
container-name: data-table;
}
.data-table {
width: 100%;
}
/* Hide lower-priority columns when space is tight */
.col-priority-low {
display: none;
}
.col-priority-medium {
display: none;
}
@container data-table (min-width: 500px) {
.col-priority-medium {
display: table-cell;
}
}
@container data-table (min-width: 800px) {
.col-priority-low {
display: table-cell;
}
}
/* Switch to card layout on very narrow containers */
@container data-table (max-width: 350px) {
.data-table,
.data-table thead,
.data-table tbody,
.data-table tr,
.data-table td {
display: block;
}
.data-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.data-table td::before {
content: attr(data-label);
font-weight: 600;
display: block;
margin-bottom: 0.25rem;
}
.data-table tr {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
}Combining Container Queries with Style Queries
Style queries (CSS @container style()) let you query the computed style of a container, enabling components to respond to state without JavaScript.
/* Style queries: respond to custom property values */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Dark variant via custom property */
.card-container[data-theme="dark"] {
--card-theme: dark;
}
@container card style(--card-theme: dark) {
.card {
background: #1e293b;
color: #f1f5f9;
}
.card-title {
color: #e2e8f0;
}
}
/* Featured variant */
.card-container[data-featured] {
--card-featured: true;
}
@container card style(--card-featured: true) {
.card {
border-left: 4px solid var(--accent-color);
background: var(--featured-bg);
}
.card-image {
aspect-ratio: 16 / 9;
}
}Nested Containers
Containers can nest, and each container query references the nearest ancestor with the matching container name.
/* Page layout: outer container */
.page-container {
container-type: inline-size;
container-name: page;
}
/* Card grid: inner container */
.card-grid-container {
container-type: inline-size;
container-name: grid;
}
/* Individual card: innermost container */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Grid responds to its container */
.card-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@container grid (min-width: 500px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@container grid (min-width: 900px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Card responds to its own container */
/* (which gets narrower as more columns appear) */
@container card (min-width: 300px) {
.card {
grid-template-columns: 120px 1fr;
}
}Migration Strategy: Media Queries to Container Queries
You don't have to rewrite everything at once. Migrate component by component, starting with the ones that appear in multiple layout contexts.
/* Step 1: Identify components that need container awareness */
/* Good candidates: cards, navigation, data tables, form layouts */
/* Bad candidates: full-page layouts (viewport is the container) */
/* Step 2: Add containment without changing behavior */
.card-wrapper {
container-type: inline-size;
/* containment has no visual effect — safe addition */
}
/* Step 3: Convert media queries to container queries one at a time */
/* Before */
@media (min-width: 768px) {
.card { grid-template-columns: 200px 1fr; }
}
/* After — test in multiple contexts */
@container (min-width: 400px) {
.card { grid-template-columns: 200px 1fr; }
}
/* Step 4: Keep media queries for truly viewport-dependent styles */
/* Page-level layouts, sticky headers, print styles */
@media (min-width: 1024px) {
.page-layout {
grid-template-columns: 280px 1fr;
}
}Key Takeaways
Container queries let components respond to their container's dimensions instead of the viewport—a card that goes horizontal at 400px of available space works correctly whether it's in a 300px sidebar (stays vertical) or an 800px content area (goes horizontal), without any code changes. Use container-type: inline-size on parent elements and write @container (min-width: ...) rules on children, combined with container query units like cqi for fluid typography and spacing that scales relative to the component's actual space rather than the screen. Start migrating the components that appear in the most varied layout contexts—cards, navigation, data tables—because these benefit most from container awareness, while page-level layouts that genuinely depend on viewport dimensions should remain with media queries. Nest containers when building complex layouts so each level responds independently—a grid that adjusts its column count based on available space contains cards that adjust their internal layout based on each card's space, creating compositions that adapt at every level without coordinating breakpoints.


