Responsive and interactive states

Use container query variants

Adapt a component to the width of its own container with built-in container query utilities and named containers.

Viewport breakpoints ask “how wide is the window?”. Container queries ask “how wide is the box I’m in?”. For reusable components, the second question is often the right one.

Mark a container and respond inside it:

<div class="@container">
  <article class="flex flex-col @md:flex-row">...</article>
</div>

@container sets container-type: inline-size on the div, making it a query container. @md:flex-row applies when that container, not the viewport, is at least Tailwind’s md container size, which is 28rem. Like breakpoint variants, container variants are minimum-width conditions.

Why this matters

The same card can now live in two places on one page. In a 20rem sidebar it stays stacked. In the main column it becomes a row. Same browser width, same markup, different layout, because each instance responds to its own space. With viewport breakpoints you’d need two different class lists or a wrapper hack.

Named containers

When containers nest, a variant matches the nearest one. If that’s not the one you mean, name it:

<section class="@container/main">
  <article class="grid @md/main:grid-cols-[10rem_1fr]">...</article>
</section>

@md/main: always measures the main container, even if a closer unnamed container exists. Name containers after layout regions, like main or sidebar, not after one page.

Containers don’t replace breakpoints

Page-wide things still depend on the viewport: the main navigation, the global gutters, whether a sidebar exists at all. A reusable card, toolbar, or widget cares about the space it was given. Pick the boundary that owns the constraint.

When @md never matches

The most common bug is a missing container. @md:* does nothing if no ancestor has @container. Inspect the parent in DevTools and check container-type in the Computed panel. Then measure the container’s content box, not the window. A container with p-8 is narrower than it looks.

Custom sizes

For a one-off threshold you can write @min-[30rem]:flex-row. If several components share a threshold, define it in the theme as --container-* so it has a name and one meaning everywhere.

Try this: render the same card in a 20rem sidebar and a flexible main column, without changing the viewport. Inspect both containers, and prove that only the wide instance activates @md:.

Lesson completed