Skip to content
React & Tailwind

Use container queries in Tailwind CSS

How to make building blocks react to their container's width instead of the browser window, what that looks like in Tailwind and where container queries reach their limits.

9 min readUpdated: 24 August 2026
Container queriesTailwindResponsiveCSS
Wrapper01
Series 04
Light that stays all day
Six lamps from one workshop in Utrecht.
See the rangeShowroom

A building block from a library ends up in many places: once at full width, once as one of three cards, once narrow in a sidebar. Media queries only help so far here, because they ask the browser window, not the room the block actually has. Container queries flip the question around. This guide shows how they work, how to use them in Tailwind and where they reach their limits.

The short version
  • Container queries ask about the width of the slot a block sits in, not the width of the window. The same block adapts wherever it lands.
  • The wrapper becomes the container, the element inside reacts. A block usually needs one extra element around it for that.
  • In Tailwind v4 they are built in: @container on the wrapper, @md: and friends on the parts. No separate plugin is needed anymore.
  • Media queries stay right for the large layout. Container queries are for the single block, and both together is the normal case.
01

Why media queries fall short here

A media query always asks the same thing: how wide is the browser window? For the large layout of a page that is exactly right. A single block, however, does not care about the window, it cares about the room it has where it sits.

Picture the same card in three places: at full width on a landing page, as one of three columns in a grid and narrow in a sidebar. The window is the same width in the grid and in the sidebar, the room for the card is not. The media query sees no difference and serves the same version in both cases.

Anyone who reuses blocks knows the result: you build special cases per placement, or the block breaks in the narrow column. Container queries solve this by measuring the container instead of the window.

A media query asks the window. A container query asks the room the block actually has.

02

What a container query measures

For an area to be queryable, it first has to declare itself a container. In plain CSS that happens through container-type: inline-size. This tells the browser: this element has a width that children may ask about.

The word inline-size matters. It stands for the width in the normal writing flow and deliberately limits the query to one direction. That is on purpose, because only then is the height free to follow from the content, without layout and query blocking each other.

The children then query with @container, not with @media. Above a certain container width other rules apply. The rest of the page and the window stay out of it.

<section className="   px-6">  <div className="mx-auto flex     flex-col      ">    <p>Eyebrow</p>    <h1>Headline</h1>    <p>Two sentences</p>    <div className="flex ">      <a>Book a call</a>      <a>Work</a>    </div>  </div></section>

Point at a highlighted class to see where it applies in the preview.

The wrapper declares itself a container. The card inside asks about its width, not the window's.

.slot {  container-type: inline-size;} @container (min-width: 28rem) {  .card {    display: flex;    gap: 1rem;  }}
Plain CSS: the wrapper becomes a container, the card inside reacts.
03

Setting it up in Tailwind

In Tailwind the detour through custom CSS falls away. The wrapper gets the @container class, and the parts inside get thresholds like @md: instead of md:. The shorthand @md there does not stand for the window but for the container's width.

The thresholds are the same names you know from the normal breakpoints, just tied to the container: @sm, @md, @lg and so on. That keeps the code readable, and the @ shows at a glance that the container is meant here, not the window.

So the setup is always the same: a wrapper with @container, and the element with the @ thresholds inside it. This split is exactly why a block from the library usually gains one extra element around it.

<div className="@container">  <article className="flex flex-col @md:flex-row @md:gap-4">    <img className="w-full @md:w-40" />    <div className="p-4">      <h3 className="text-base @md:text-lg">Titel</h3>    </div>  </article></div>
The same card in Tailwind. The wrapper carries @container, the parts the thresholds.
Built in as of v4In Tailwind v4 you no longer need a plugin. The @container class sets container-type to inline-size, and the @ variants are ready to go. In v3 that still ran through the @tailwindcss/container-queries plugin.

Great heroes remove decisions.

Three arrangements that work. Which one fits is decided by the imagery, not by taste.

SplitText left, image right.
CenteredOne axis, short lines.
EditorialDeliberately off centre.
04

Named containers for nested cases

A threshold like @md: always asks the nearest container above it. As long as there is only one, that is unambiguous. As soon as containers nest, it can be the wrong one: a card that is itself a container, with a media area inside it as another container, quickly leads to a part asking the inner one instead of the outer.

That is what names are for. The wrapper gets @container/card, the media area @container/media, and the parts query on purpose with @md/card: or @lg/card:. So the code says which container is meant, and the query does not jump to the nearest one.

In practice a name pays off as soon as a block itself contains blocks again. A single, flat container is fine without a name.

<div className="@container/card">  <div className="@container/media">    <img className="w-full @md/card:w-40" />  </div>  <p className="text-sm @lg/card:text-base">Beschreibung</p></div>
A name makes sure the parts ask the right container.
Without a name the nearest container countsIf you nest containers and leave the names out, every threshold queries the nearest container above it. That is rarely the one you mean. As soon as two containers nest, give them names.
05

Units that grow along

Container queries switch rules at thresholds. Sometimes, though, you want no jump but a smooth ramp: type or spacing that grows with the container's width. There are dedicated units for that.

The most useful is cqi, one percent of the container's inline width. In a clamp between a smallest and a largest value it gives a font size that follows the room the block has, not the window. Alongside it there are cqw and cqb as well as cqmin and cqmax for the smaller or larger edge.

That way a heading stays calm in the narrow column and grows large at full width, without maintaining a separate size per placement.

.card h3 {  font-size: clamp(1rem, 4cqi, 1.5rem);}
Font size tied to the container width, with a floor and a ceiling.

cqi measures the container, not the window. The type follows the slot it sits in.

06

Where container queries reach their limits

Container queries have been available in all major browsers since 2023, so you can use the width query without reservation. A few rules of the mechanics are still worth knowing, otherwise you look for the bug in the wrong place.

Slightly off
  • The element gets @container and the thresholds at once. Nothing happens, because a container only measures its children.
  • Set container-type: inline-size and then query the height. The height stays out of reach.
  • Every block is turned into a container just in case. That costs compute in places where the width is fixed anyway.
Works
  • A wrapper becomes the container, the element inside reacts. One extra div is enough.
  • For the width, inline-size is enough. Only where the height really matters does size with a known height come in.
  • Containers only where the same block lands in slots of different widths.
Do not turn grid and flex children into containers directlyAn element that is both a grid or flex child and a container can lose its size and collapse. Put the container on an extra element inside it, not on the child itself.
07

When to use which query

Container queries do not replace media queries, they complement them. The question is not either or, but which question fits the matter.

  1. Same block, different slots. This is where the container query fits: a card meant to sit equally well in a grid, a sidebar and at full width.
  2. The large layout of the page. Number of columns, navigation and the margins at the window edge stay the media query's job. They relate to the device, not to a single block.
  3. Both together. The normal case: the media query arranges the grid, the container query shapes the block inside it. They do not exclude each other.
  4. Device level details. prefers-reduced-motion, prefers-color-scheme and pointer precision still query the device, not the container.

For a library of building blocks the room is usually the right question, not the window.

Share
Written by
Amelie RoesmannCreative directionLeonie RoesmannDesign engineering

We build websites and web apps at Systra Studios in Münster and put the building blocks from that work here. What you read comes from real projects, not from a brochure.

Systra Studios: web design from Münster

Where to go next

Everything from this guide sits ready in the library. Six sections per category run live and full size without an account, the source code comes with premium.

Keep reading
FAQ

Frequently asked questions

A media query asks the browser window, a container query asks the width of the nearest ancestor container. The difference matters as soon as the same block sits in places of different widths: to the window a narrow sidebar looks the same as full width, to the container it does not. Only the container query can tell the two cases apart.

Didn't find your question? We're happy to help.

Get in touch