For a long time, building truly reusable UI components with Tailwind meant fighting a fundamental mismatch: viewport-based breakpoints describe the browser window, not the space a component actually occupies. A card sitting in a sidebar has completely different layout needs than the same card spanning a full-width grid — yet with md: and lg:, you can only ask "how wide is the screen?" Container queries let you ask a better question: "how wide is my container?"
In Tailwind CSS v4, container query support moved from a separate plugin (@tailwindcss/container-queries) into the framework core. If you've been putting off learning them, now's the moment — the API is clean, the browser support is solid (Chrome 105+, Firefox 110+, Safari 16+), and there's nothing extra to install.
The core idea: @container and why it matters
A container query works in two steps. First, you designate a parent element as a containment context — you're telling the browser "measure this element's width and make it available to my descendants." Then you write styles on those descendants that activate at certain container widths, just like you'd write md:flex-row for a viewport breakpoint.
This means a component can react to its available space rather than the screen size. If that component is in a narrow sidebar, it stacks. If it's in a wide content area, it lays out horizontally. Same markup, same classes, no JavaScript, no media query overrides.
Basic usage
Add the @container class to any element to make it a containment context (technically, it sets container-type: inline-size). Then use @sm:, @md:, @lg:, and so on to apply styles to children based on that container's width:
<div class="@container">
<div class="flex flex-col @md:flex-row gap-4">
<img class="w-full @md:w-48 rounded-lg" src="..." alt="..." />
<div>
<h2 class="text-lg font-semibold">Service title</h2>
<p class="text-sm text-gray-600">Description text here.</p>
</div>
</div>
</div>
The inner div stacks vertically by default, then switches to a horizontal row once its container hits 28rem (448px) wide. Drop this component into a three-column grid or a narrow sidebar — it adapts automatically without touching the markup.
Container breakpoint sizes
The Tailwind v4 container size scale runs from @3xs to @7xl. These are intentionally smaller than the viewport breakpoints — because you're sizing a component, not a screen:
@3xs— 16rem (256px)@2xs— 18rem (288px)@xs— 20rem (320px)@sm— 24rem (384px)@md— 28rem (448px)@lg— 32rem (512px)@xl— 36rem (576px)@2xl— 42rem (672px)@3xl— 48rem (768px)@4xl— 56rem (896px)@5xl— 64rem (1024px)@6xl— 72rem (1152px)@7xl— 80rem (1280px)
Like viewport breakpoints, container query variants are mobile-first — a class like @md:flex-row means "apply at @md and above." All of them compile down to the modern @container (width >= …) syntax.
Max-width variants
Need to apply a style below a certain size? The @max-* variants invert the logic:
<div class="@container">
<!-- Default: horizontal row. Below @md: stack vertically -->
<div class="flex flex-row @max-md:flex-col gap-4">
<!-- ... -->
</div>
</div>
You can also stack a min and max variant together to target a specific range — useful when you need a layout that only applies in an intermediate size band:
<div class="@container">
<!-- Only hidden between @sm and @md -->
<aside class="block @sm:@max-md:hidden">
<!-- sidebar content -->
</aside>
</div>
Named containers
When you have nested containers, Tailwind applies styles based on the nearest ancestor container by default. Sometimes that's the wrong one — you might want a deeply nested element to respond to a grandparent container instead.
Named containers solve this. Add a name with @container/{name} and reference it in variants with @sm/{name}::
<div class="@container/card">
<div class="p-4">
<!-- This responds to /card, not the nearest container -->
<div class="hidden @lg/card:block">
Extended content shown when the card container is wide
</div>
</div>
</div>
A common real-world pattern: a page layout might have both a content area container and a sidebar container. A component inside the sidebar can be told to respond to the page-level container rather than the sidebar, letting it make layout decisions based on the broader context.
Arbitrary values
For one-off sizes that don't fit the named scale, use @min-[…] and @max-[…] with any valid CSS length:
<div class="@container">
<div class="grid grid-cols-1 @min-[500px]:grid-cols-2 @min-[900px]:grid-cols-3">
<!-- ... -->
</div>
</div>
You can also define custom container sizes globally using CSS theme variables:
@import "tailwindcss";
@theme {
--container-8xl: 96rem;
}
After that, @8xl: works as a variant alongside the built-in scale.
A practical example: a service card component
Here's a complete, copy-pasteable card that adapts to three layout states based on container width — compact tile, medium card, and full horizontal layout:
<!-- Drop this anywhere. It adapts to whatever space it has. -->
<div class="@container rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="flex flex-col @sm:flex-row gap-0">
<!-- Image -->
<div class="shrink-0">
<img
src="/images/hvac-service.jpg"
alt="HVAC service"
class="w-full @sm:w-40 @lg:w-56 h-48 @sm:h-full object-cover rounded-t-xl @sm:rounded-l-xl @sm:rounded-tr-none"
/>
</div>
<!-- Content -->
<div class="flex flex-col justify-between p-4 @lg:p-6 gap-3">
<div>
<span class="text-xs font-medium text-blue-600 uppercase tracking-wide">
HVAC
</span>
<h3 class="mt-1 text-base @lg:text-xl font-semibold text-gray-900">
AC Tune-Up & Inspection
</h3>
<p class="mt-2 text-sm text-gray-600 @max-sm:line-clamp-2">
A full 21-point inspection, refrigerant check, coil cleaning, and efficiency
report. Most systems serviced same day.
</p>
</div>
<!-- Footer: price + CTA -->
<div class="flex items-center justify-between gap-4">
<span class="text-lg font-bold text-gray-900">From $89</span>
<a
href="/services/ac-tune-up"
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
>
Book now
</a>
</div>
</div>
</div>
</div>
At a narrow container (below @sm, 384px): the image sits on top, the description truncates to two lines. At @sm and above: image shifts to the left in a horizontal row. At @lg (512px) and above: the image widens, padding increases, and the heading bumps to text-xl. The same card works in a full-width hero, a two-column grid, and a narrow widget rail — with no media queries, no wrapper class overrides.
Container query units
A lesser-known feature: you can use container query length units like cqw (container query width) and cqi (container query inline size) as arbitrary values in utility classes to size things relative to the container rather than the viewport:
<div class="@container">
<!-- Always 60% of the container's width, not the screen -->
<div class="w-[60cqw]"></div>
</div>
For units that depend on the block (vertical) size — cqb, cqh — swap @container for @container-size, which sets container-type: size and makes both dimensions available.
When to use container queries vs. viewport breakpoints
Container queries and viewport breakpoints serve different jobs, and knowing which to reach for matters:
- Use viewport breakpoints for page-level layout decisions: the sidebar appears at
lg, the nav collapses atmd. These are genuinely about the screen. - Use container queries for component-level layout: cards, service listings, feature blocks, testimonials — anything that might appear in multiple contexts with different available widths.
- Avoid double-nesting containers carelessly. Every
@containerancestor creates a boundary; a deeply nested component will respond to the nearest one. Use named containers when the nearest boundary is wrong. - Don't mix concerns in one element. An element can't be both a container and respond to a container query from a sibling — containment applies to descendants, not the element itself.
Migration from the v3 plugin
If you're upgrading a project that used @tailwindcss/container-queries, the good news is the class names are identical. Remove the plugin import from your config, and your existing @container, @sm:, @md: classes keep working with no changes. The built-in scale actually extends the old plugin's range with smaller sizes (@3xs, @2xs, @xs) that weren't in the plugin.
Container queries have crossed from "experimental CSS trick" to standard practice, and Tailwind v4 makes them as natural as any other responsive variant. The workflow is simple: identify components that appear in more than one context, wrap them in @container, and replace your viewport-based assumptions with container-aware ones. The resulting components are genuinely portable — and that portability compounds as your component library grows.