Container queries
clamp() with container query units
Swap vw for cqi and a fluid value scales with its component instead of the browser window.
A normal fluid value uses vw, so it follows the width of the browser window. That is right for page gutters and big headings. It is wrong for a component that can sit in a narrow sidebar on one page and a wide main column on the next. The window is the same size in both places, so the component gets the same padding and font size in both.
Container query units fix that. They measure a parent element instead of the window, and they work inside clamp() like any other unit.
The units
Each unit is 1% of a dimension of the nearest query container, the same way 1vw is 1% of the viewport:
cqw: 1% of the container’s width.cqh: 1% of the container’s height.cqi: 1% of the container’s inline size. In a horizontal writing mode, that is the width.cqb: 1% of the container’s block size. In a horizontal writing mode, that is the height.cqminandcqmax: the smaller or larger ofcqiandcqb.
For fluid values, cqi is the one you want almost every time. It gives the same result as cqw on most sites, and it keeps working if the text runs vertically.
Making a container
An element becomes a query container when you give it a container-type:
.card-slot {
container-type: inline-size;
}inline-sizelets descendants measure its width. This is the one you need forcqi.sizelets them measure both width and height, but the element can no longer take its height from its content. You usually need to give it a height yourself.normalis the default. The element is not a size container.
From vw to cqi
The formula does not change. The only difference is what the range means: instead of the smallest and largest screen, you pick the smallest and largest container width.
Say a card should have 16px of padding at 320px wide and 32px at 800px wide, with a title that grows from 20px to 28px over the same range:
slope = (32 - 16) / (800 - 320) = 0.03333
intercept = 16 - 0.03333 × 320 = 5.333px = 0.333rem
preferred = 0.333rem + 3.333cqiYou can use the clamp() generator for this. Enter the container widths as the viewport range, copy the value and replace vw with cqi. The deep dive explains the formula step by step.
A full example
The same card is used in a sidebar and in the main column. Each slot is a container, so each card sizes itself to the slot it is in:
.layout {
display: grid;
grid-template-columns: 20rem 1fr;
gap: 2rem;
}
.sidebar,
.main {
container-type: inline-size;
}
.card {
padding: clamp(1rem, 0.333rem + 3.333cqi, 2rem);
}
.card h2 {
font-size: clamp(1.25rem, 0.917rem + 1.667cqi, 1.75rem);
}<div class="layout">
<aside class="sidebar">
<article class="card"><h2>In the sidebar</h2></article>
</aside>
<main class="main">
<article class="card"><h2>In the main column</h2></article>
</main>
</div>In the 320px sidebar, the card gets 16px of padding and a 20px title. In a main column of 800px or wider, it gets 32px and 28px. At 500px, it gets 22px of padding. The window size does not matter, only the slot.
The generator on this site uses the same trick for its preview. The emulated screen is an @container, and the generated value is shown with vw swapped for cqw. That is how it can show a value at 1152px while your window has a different size.
Gotchas
The element needs a container above it
Container query units resolve against the nearest ancestor that is a query container for that axis. If there is none, they fall back to the small viewport units: cqi behaves like svi, cqb like svb. Nothing breaks, but the value quietly follows the window instead of the component. If a value does not change when the container does, check that a parent really has container-type.
A container cannot use its own units
An element is never its own query container. If .card has container-type: inline-size, its own padding in cqi is measured against the next container further up, or the viewport. Only its children measure the card. Put the container on a wrapper, like the slots in the example above, when the card itself should scale.
Containers do not size to their content
container-type: inline-size tells the browser the element’s width does not depend on its content. That is what makes the units possible, but it means a container whose width would normally come from its content can collapse to nothing. Watch out for inline-block elements, floats and flex items that are not set to grow. Block elements and grid cells are fine.
cqb and cqh need a size container
An inline-size container only answers questions about its width. A cqb or cqh value skips it and looks further up for a container-type: size element, falling back to the viewport if there is none.
Units cannot target a named container
You can name a container with container-name and target it in an @container query. Container query units have no such option. They always use the nearest eligible container.
Font sizes still need a rem part
Zooming in makes a fluid container fewer CSS pixels wide, exactly like the viewport. A font size in cqi alone would not grow when people zoom, so keep the rem minimum, maximum and intercept. The fluid typography accessibility guide covers this in detail.
Tailwind CSS
Tailwind CSS v4 has container queries built in. The @container class sets container-type: inline-size, and cqi works in any arbitrary value. Leave out the spaces:
<aside class="@container">
<article class="p-[clamp(1rem,0.333rem+3.333cqi,2rem)]">
<h2 class="text-[clamp(1.25rem,0.917rem+1.667cqi,1.75rem)]">Card title</h2>
</article>
</aside>If you use the values more than once, make them theme tokens:
@theme {
--spacing-card: clamp(1rem, 0.333rem + 3.333cqi, 2rem);
--text-card-title: clamp(1.25rem, 0.917rem + 1.667cqi, 1.75rem);
}That gives you p-card and text-card-title. They only follow the container when an ancestor has the @container class. The Tailwind CSS clamp() guide covers arbitrary values and tokens in more depth.
Browser support
Container query units and container-type work in all current versions of Chrome, Edge, Firefox and Safari. Firefox was the last to add them, in early 2023.
Summary
cqiis 1% of the container’s width, the wayvwis 1% of the window’s.- Give a parent
container-type: inline-size, then usecqiinstead ofvwinsideclamp(). - Pick the range in container widths, not screen widths.
- Without a container, the units fall back to small viewport units. An element never measures itself.
- Keep a
rempart in font sizes so they still grow with zoom.
Container query units FAQ
What is the cqi unit in CSS?
cqi is 1% of the inline size of the nearest query container, which is its width in horizontal writing modes. It works like vw, but measures a parent element with container-type set instead of the browser window.
Can I use container query units inside clamp()?
Yes. Container query units work anywhere a length works, including inside clamp(). Replace vw with cqi, for example clamp(1rem, 0.333rem + 3.333cqi, 2rem), and the value scales with the container instead of the window.
What is the difference between cqw and cqi?
cqw always measures the physical width of the container. cqi measures its inline size, which is the width in horizontal writing modes and the height in vertical ones. For most sites they give the same result; cqi is the safer default.
What happens to cqi when there is no container?
If no ancestor is a query container for that axis, container query units fall back to the small viewport units. cqi then behaves like svi, which is the same as svw in horizontal writing modes, so the value scales with the window instead.
How do I use container query units in Tailwind CSS?
Add the @container class to the parent, which sets container-type: inline-size in Tailwind CSS v4, and use cqi in an arbitrary value on the child, like p-[clamp(1rem,0.333rem+3.333cqi,2rem)]. You can also put the value in an @theme token.