Viewport breakpoints answer one question: how wide is the screen? Components, however, live in sidebars, modals and grid cells whose width has little to do with the screen. Container queries fix this by letting an element respond to the size of its parent, and Tailwind v4 supports them out of the box. In this lesson you will learn the @container workflow, the max-* and range variants, and how to add, rename or remove breakpoints through the theme.
A product card that switches from stacked to side-by-side at md: looks right in a full-width grid but wrong inside a narrow sidebar on the same page: both are at the same viewport width. With container queries the card asks "how wide is the box I am in?" and the same markup works everywhere. This makes components truly reusable, which is exactly the goal of a utility-first workflow.
Mark the parent as a container, then use @-prefixed variants on descendants:
<div class="@container">
<div class="grid grid-cols-1 gap-4 @sm:grid-cols-2 @xl:grid-cols-4">
<div>Item</div>
<div>Item</div>
</div>
</div>The container sizes mirror the familiar names but are based on the parent's width: @xs (20rem), @sm (24rem), @md (28rem), @lg (32rem), @xl (36rem), @2xl (42rem) up to @7xl (80rem), plus @3xs and @2xs for very small slots. Like breakpoints, they are mobile-first: @md:flex-row applies when the container is at least 28rem wide.
Containers can be named so a deeply nested element can target a specific ancestor:
<aside class="@container/sidebar">
<div class="@container/panel">
<p class="@lg/sidebar:text-base @sm/panel:font-semibold">Targets two different containers</p>
</div>
</aside>Arbitrary sizes are written @min-[30rem]: and @max-[50rem]:, and @max-md: applies below a size. The @container class sets container-type: inline-size, so the element's height is no longer used for its own sizing — keep that in mind if a container must grow with its content vertically.
Viewport breakpoints also gained a downward form in v4. max-md: applies below the md breakpoint, and stacking a min and a max targets a range:
<p class="max-md:text-center">Centred on phones only</p>
<nav class="md:max-lg:hidden">Hidden only on tablets</nav>
<div class="min-[600px]:grid max-[900px]:gap-2">One-off arbitrary breakpoints</div>| Variant | Media query |
|---|---|
| md: | (width >= 48rem) |
| max-md: | (width < 48rem) |
| md:max-xl: | (width >= 48rem) and (width < 80rem) |
| min-[600px]: | (width >= 600px) |
Prefer the mobile-first md: style for most layout and reserve max-* for the occasional override; mixing both styles freely makes the cascade hard to read.
Breakpoints are theme variables in the --breakpoint-* namespace, so adding one is a single line:
@import "tailwindcss";
@theme {
--breakpoint-xs: 30rem; /* adds xs: and max-xs: */
--breakpoint-3xl: 120rem; /* adds 3xl: for very wide screens */
--breakpoint-2xl: 100rem; /* overrides the default 96rem */
}To remove a default breakpoint set it to initial (--breakpoint-2xl: initial;), and to replace the whole set start with --breakpoint-*: initial;. Keep every breakpoint in the same unit — Tailwind sorts them numerically, and mixing px and rem produces an inconsistent order. Container sizes live in the --container-* namespace and are customised the same way; note that those variables also drive max-w-* utilities.
@container on a grid cell or flex child needs a width; give it min-w-0 if it is a flex item that should shrink.Which class turns an element into a query container for its descendants?
@container to the parent and @md:-style variants to children.@container/name, @lg/name:) resolve nested situations; @min-[…] and @max-[…] give arbitrary sizes.max-md: and ranges like md:max-lg: complement the mobile-first md: variants.--breakpoint-* variables in @theme; set one to initial to remove it and keep units consistent.Next lesson: Hover, Focus and Other States — style interactive states with hover:, focus:, active: and disabled:.