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.
- 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.
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.
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; }}
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>
Great heroes remove decisions.
Three arrangements that work. Which one fits is decided by the imagery, not by taste.
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>
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);}
cqi measures the container, not the window. The type follows the slot it sits in.
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.
- 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.
- 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.
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.
- 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.
- 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.
- 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.
- 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.




