Container Queries
Style components based on the width of their container instead of the viewport — built into Tailwind v4 with no plugin required.
Core in v4 Container queries let a component respond to the space it has been given, not to the size of the browser window.
Overview#
Mark an element as a container, then style its descendants with @-prefixed variants:
<div class="@container">
<div class="flex flex-col @md:flex-row">
<!-- stacks until the container is 28rem wide -->
</div>
</div>Why they exist#
A breakpoint asks how wide is the window? A component usually needs to know how wide am I? Those differ the moment the same component appears in a sidebar and in a main column.
<div class="space-y-6 bg-gray-50 p-4">
<!-- Wide slot: the card lays out horizontally -->
<div class="@container mx-auto max-w-2xl">
<article class="flex flex-col gap-4 rounded-xl border border-gray-200 bg-white p-4 @md:flex-row @md:items-center">
<div class="h-24 shrink-0 rounded-lg bg-indigo-100 @md:w-32"></div>
<div>
<h3 class="font-semibold text-gray-900">Wide container</h3>
<p class="text-sm text-gray-600">Same markup, laid out as a row.</p>
</div>
</article>
</div>
<!-- Narrow slot: identical markup, stacked -->
<div class="@container mx-auto max-w-xs">
<article class="flex flex-col gap-4 rounded-xl border border-gray-200 bg-white p-4 @md:flex-row @md:items-center">
<div class="h-24 shrink-0 rounded-lg bg-indigo-100 @md:w-32"></div>
<div>
<h3 class="font-semibold text-gray-900">Narrow container</h3>
<p class="text-sm text-gray-600">Same markup, stacked.</p>
</div>
</article>
</div>
</div>With viewport breakpoints both cards would look the same, because the window is one width.
Container sizes#
| Variant | Min width | Variant | Min width |
|---|---|---|---|
@3xs | 16rem | @2xl | 42rem |
@2xs | 18rem | @3xl | 48rem |
@xs | 20rem | @4xl | 56rem |
@sm | 24rem | @5xl | 64rem |
@md | 28rem | @6xl | 72rem |
@lg | 32rem | @7xl | 80rem |
@xl | 36rem |
Every size also has a @max-* counterpart: @max-md is @container (width < 28rem).
Common patterns#
Max-width container queries#
<div class="@container">
<div class="flex flex-row @max-md:flex-col">…</div>
</div>Ranges#
<div class="@container">
<div class="flex flex-row @sm:@max-md:flex-col">…</div>
</div>Named containers#
When containers nest, name them so a descendant can target the right one.
<div class="@container/sidebar">
<div class="@container/card">
<p class="@sm/card:text-lg @lg/sidebar:font-bold">…</p>
</div>
</div>Arbitrary sizes#
<div class="@container">
<div class="flex flex-col @min-[475px]:flex-row">…</div>
</div>Container query units#
cqw and cqh are percentages of the container, usable in arbitrary values:
<div class="@container">
<div class="w-[50cqw]">Half the container's width</div>
</div>Size containers#
v4.3 @container creates an inline-size container, which only tracks width. When you need block-size units such as cqb, use @container-size:
<div class="@container-size">
<div class="h-[50cqb]">Half the container's height</div>
</div>Customising container sizes#
Container sizes live in the --container-* namespace — the same namespace that powers max-w-*.
@import "tailwindcss";
@theme {
--container-8xl: 96rem;
}That gives you both @8xl:* and max-w-8xl.
Advanced example#
A product card that reflows through three layouts based on its own width:
<div class="@container mx-auto max-w-md p-4">
<article class="overflow-hidden rounded-xl border border-gray-200 bg-white @sm:flex">
<div class="h-32 bg-gradient-to-br from-indigo-500 to-purple-600 @sm:h-auto @sm:w-40 @sm:shrink-0"></div>
<div class="p-4 @lg:p-6">
<span class="text-xs font-semibold tracking-wide text-indigo-600 uppercase">New</span>
<h3 class="mt-1 font-semibold text-gray-900 @lg:text-lg">Aurora Desk Lamp</h3>
<p class="mt-1 text-sm text-gray-600 @max-sm:line-clamp-2">
Warm dimmable light with a machined aluminium arm and a weighted base.
</p>
<div class="mt-3 flex items-center justify-between gap-3">
<span class="font-bold text-gray-900">$129</span>
<button class="rounded-md bg-gray-900 px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-700">
Add
</button>
</div>
</div>
</article>
</div>Comparison#
md: (breakpoint) | @md: (container) | |
|---|---|---|
| Measures | Viewport width | Nearest @container ancestor |
| Needs a marked ancestor | No | Yes — @container |
| Survives being moved into a sidebar | No | Yes |
| Right for | Page shell, global nav | Cards, widgets, list items |
Common mistakes#
- Forgetting
@containeron the parent. Without it the variants match nothing — silently. - Querying the element itself. A container queries its descendants. Put
@containeron the wrapper, not on the element you are styling. - Using
cqbwithout@container-size. Block-size units need a size container. - Replacing all breakpoints with container queries. Page-level structure still belongs to the viewport.
Tips#
- Wrap the outermost element of a reusable component in
@containeras a matter of course. It costs nothing until you use a variant. - Container queries compose with everything else:
dark:@lg:hover:bg-gray-800is valid. @containersetscontainer-type: inline-size, which establishes containment — a rare source of surprise with absolutely positioned children.
Related topics#
- Responsive Design — viewport breakpoints
- Cards — where container queries pay off most
- Variant Reference — every container variant