CSS Clamp

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.
  • cqmin and cqmax: the smaller or larger of cqi and cqb.

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-size lets descendants measure its width. This is the one you need for cqi.
  • size lets 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.
  • normal is 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.333cqi

You 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

  • cqi is 1% of the container’s width, the way vw is 1% of the window’s.
  • Give a parent container-type: inline-size, then use cqi instead of vw inside clamp().
  • 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 rem part 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.