The Problem With Media Queries
Media queries respond to the viewport. But components don't care about the viewport—they care about the space they have.
/* This breaks when a card is in a narrow sidebar vs. a wide grid */
@media (max-width: 768px) {
.card { flex-direction: column; }
}
/* The card is always the same width... until the layout changes */
You end up passing context down via CSS classes:
<div class="card card--narrow"> <!-- in sidebar -->
<div class="card card--wide"> <!-- in main content -->
Container queries solve this at the CSS level.
Container Queries Basics
/* 1. Declare a containment context */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 2. Style the child based on container width */
.card {
display: flex;
flex-direction: row;
gap: 16px;
}
@container card (max-width: 400px) {
.card {
flex-direction: column;
}
}
@container card (max-width: 250px) {
.card__image {
display: none;
}
}
Now the card adapts to its container, not the viewport. Put it in a narrow sidebar or a wide grid—it responds correctly.
Practical Example: Responsive Card Grid
<div class="grid">
<article class="card-container">
<div class="card">
<img src="..." class="card__image" />
<div class="card__body">
<h2>Title</h2>
<p>Description</p>
<button>Read more</button>
</div>
</div>
</article>
</div>
.card-container {
container-type: inline-size;
}
/* Default: stacked */
.card {
display: grid;
grid-template-rows: auto 1fr;
}
.card__image {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
}
/* Wide card: side-by-side */
@container (min-width: 500px) {
.card {
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr;
}
.card__image {
height: 100%;
aspect-ratio: auto;
}
}
/* Very wide card: bigger image */
@container (min-width: 700px) {
.card {
grid-template-columns: 300px 1fr;
}
}
Container Query Units
/* cqi = 1% of container's inline size */
/* cqb = 1% of container's block size */
/* cqw, cqh = container width/height */
.card__title {
font-size: clamp(1rem, 4cqi, 2rem);
/* Scales with container width, clamped between 1rem and 2rem */
}
.card__image {
width: 40cqi; /* 40% of container width */
}
Style Queries (New)
/* Query a custom property value */
@container style(--variant: featured) {
.card {
border: 2px solid gold;
background: #fffbeb;
}
}
<div style="--variant: featured">
<div class="card"><!-- gets gold border --></div>
</div>
Browser Support
Container queries (size) are supported in all modern browsers since 2023:
- Chrome 105+
- Firefox 110+
- Safari 16+
For older browser support:
/* Fallback for older browsers */
.card { flex-direction: column; }
/* Progressive enhancement */
@supports (container-type: inline-size) {
.card-container { container-type: inline-size; }
@container (min-width: 500px) {
.card { flex-direction: row; }
}
}
Tailwind CSS Support
# Tailwind v3.2+ supports container queries
npm install @tailwindcss/container-queries
<div class="@container">
<div class="flex flex-col @[500px]:flex-row gap-4">
<img class="w-full @[500px]:w-48" />
<div>content</div>
</div>
</div>
When to Use Container Queries vs Media Queries
Container queries:
- Reusable components that appear in different layout contexts
- UI libraries/design systems
- Components in sidebars, modals, grids
Media queries:
- Page-level layout changes (sidebar vs. no sidebar)
- Font size scaling
- Navigation patterns (hamburger menu)
- Print styles
Container queries don't replace media queries—they complement them. Use both.
Tailwind CSS with container query support pre-configured: Whoff Agents AI SaaS Starter Kit.
Top comments (0)