Container Query Units Explained: cqi, cqb, cqw, cqh, cqmin and cqmax

Problem statement

You have a card that lives in a sidebar on one screen and spans the full content column on another, and you want its padding, heading size, and gap to scale with the space the card actually occupies rather than with the viewport. Viewport units (vw, vh) only know about the screen, so a card in a 280px sidebar gets the same 4vw padding as a card in a 1200px hero. Container query length units solve exactly this: they resolve against the measured size of the nearest query container, letting one component scale correctly in every slot. This guide sits under Container Query Syntax Basics, part of Mastering Container Queries & Responsive Layouts, and focuses narrowly on the six length units and how each resolves.

Approach rationale

The six units — cqw, cqh, cqi, cqb, cqmin, cqmax — each represent 1% of a dimension of the query container. cqw and cqh are the physical width and height. cqi and cqb are the logical inline and block sizes, which track the writing mode rather than the screen axis. cqmin resolves to the smaller of cqi/cqb, and cqmax to the larger. Because these units read the container box directly, you get continuous fluid scaling with no breakpoints and no JavaScript ResizeObserver callback. That matters for accessibility too: a ResizeObserver-driven resize fires layout work on the main thread and can lag behind the paint, producing visible jumps for users on low-powered devices, whereas container units are resolved by the engine during layout in a single pass.

Two properties of these units are easy to miss and both are useful. They are ordinary <length> values, so they compose anywhere a length is valid — inside calc(), min(), max(), clamp(), in border-radius, gap, translate, box-shadow offsets, even in a @media condition's operand. And they are entirely independent of @container blocks: declaring a container makes its size readable as a unit whether or not you ever write a conditional rule against it. A component with no @container rules at all can still be fully fluid on cqi alone, which is often the cleaner design — continuous scaling has no threshold to get wrong.

The one caveat is unbounded scaling. A raw font-size: 5cqi will keep growing as the container widens, eventually overflowing the line. The fix is to compose the unit inside clamp() so it has a floor and a ceiling — which is also how you combine it cleanly with the techniques covered in the guide to fluid typography with clamp().

Complete working implementation

Container query length axes A container box showing the inline axis resolving cqi and the block axis resolving cqb, with cqmin and cqmax derived from them. How cqi and cqb map to a query container query container container-type: inline-size inline = 100cqi block = 100cqb cqmin = min cqmax = max

The component below is self-contained: drop it into an empty HTML file and resize the browser. Both cards use identical CSS, yet each scales to the column it lands in.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  .layout {
    display: grid;
    grid-template-columns: 1fr 3fr; /* sidebar, then main */
    gap: 2rem;
    padding: 2rem;
  }

  /* Each slot becomes a query container so cq* units read ITS size, not the viewport */
  .slot {
    container-type: inline-size;
  }

  .card {
    /* Padding scales with the container's inline size, clamped to sane bounds */
    padding: clamp(0.75rem, 4cqi, 2rem);
    /* Gap also tracks inline size, but never collapses below 0.5rem */
    display: grid;
    gap: clamp(0.5rem, 2cqi, 1.25rem);
    border-radius: clamp(8px, 2cqi, 18px);
    background: #11141c;
    color: #e8ecf4;
  }

  .card h2 {
    /* Fluid heading: floor 1.1rem, scales at 6% of inline size, ceiling 2rem */
    font-size: clamp(1.1rem, 6cqi, 2rem);
    line-height: 1.15;
    margin: 0;
  }

  .card p {
    /* cqmin keeps body text sensible even in a tall, narrow container */
    font-size: clamp(0.9rem, 1.6cqmin + 0.6rem, 1.05rem);
    margin: 0;
    opacity: 0.85;
  }

  /* cqb (block axis) used for a top accent bar that grows with container height */
  .card::before {
    content: "";
    display: block;
    height: clamp(3px, 1cqb, 10px);
    background: #7aa2ff;
    border-radius: 999px;
  }
</style>
</head>
<body>
  <div class="layout">
    <div class="slot">
      <article class="card">
        <h2>Sidebar card</h2>
        <p>Narrow container: cqi is small, so padding and heading stay compact.</p>
      </article>
    </div>
    <div class="slot">
      <article class="card">
        <h2>Main column card</h2>
        <p>Wide container: the same cqi rules produce larger type, gaps and padding.</p>
      </article>
    </div>
  </div>
</body>
</html>

Key technique callout

The contrast is immediate when you can drag the container. The first line is sized in cqi, the second in pixels.

Live democqi units versus a fixed size
Drag the frame: the top heading scales with the container, the bottom one never changes. Drag the bottom-right corner to resize the frame in either direction.

The unit that does the real work here is cqi. It is defined as 1% of the query container's inline size — the dimension along which text flows. In the default left-to-right, horizontal writing mode that is the container's width, so 6cqi is 6% of the card's rendered width. Because both cards share the same CSS but live in differently sized .slot containers, the engine resolves 6cqi to a different pixel value for each, with no media query and no script. Wrapping every cqi value in clamp(min, preferred, max) is what makes this production-safe: the middle argument supplies the fluid behavior while the floor and ceiling guarantee the component never produces unreadably small or comically large text. Note that container-type: inline-size only establishes the inline axis as a query dimension; cqb still resolves against the element's natural block size, which is why the accent bar above scales gently rather than dramatically.

The six units at a glance

Unit1% ofUse it when
cqicontainer inline sizeThe default for fluid type, padding and gaps in a component.
cqbcontainer block sizeThe container's height is genuinely determinate and meaningful.
cqwcontainer width (physical)You need a physical axis regardless of writing mode — rare.
cqhcontainer height (physical)Same, for the physical vertical axis — rarer still.
cqminthe smaller of cqi and cqbA value must stay modest in whichever direction is tightest.
cqmaxthe larger of cqi and cqbA decorative element should track the container's long side.

cqmin is the one worth knowing beyond cqi. Because it takes the smaller axis, it behaves like a "how cramped is this box overall" signal: a square-ish avatar sized in cqmin never overflows a container that is wide but short, whereas the same value in cqi would. cqmax is its mirror and shows up mostly in decoration — a diagonal accent, a background gradient stop, a shadow spread that should feel proportional to the container's dominant dimension.

What the units resolve against when there is no container

Container units always have a defined value, which is a mercy and occasionally a trap. If no ancestor of the element is a query container, the units fall back to the corresponding small viewport units — cqi behaves as svi, cqb as svb. Nothing errors; the component simply scales against the screen instead of its slot, which looks plausible in a full-width demo and wrong the moment the component is placed in a sidebar. The practical consequence is that a component using cq* units carries an unwritten requirement: something above me must be a container. Declare that container in the same file as the component whenever you can, so the two cannot drift apart.

The other resolution rule follows from containment rather than from the units. A cqb value can only reflect a real height when the container's type covers the block axis; otherwise there is no contained block size to take a percentage of, and the value degrades to the viewport fallback rather than tracking the box you were looking at. That is why the accent bar in the example above scales gently — it is a decoration, chosen deliberately as something that looks fine either way. Reach for cqb in load-bearing sizing only on a container whose block axis is genuinely determinate, which is the decision covered in choosing container-type: inline-size, size or normal.

Variation or extension

Under a vertical writing mode the physical and logical units diverge, and this is precisely why cqi is the safer default. Add the rule below and the inline axis becomes vertical, so cqi now tracks height while cqw would still track width.

/* RTL / vertical writing: cqi follows the inline (now vertical) axis automatically */
.slot {
  container-type: inline-size;
}

.vertical .slot {
  writing-mode: vertical-rl;
}

/* This heading scales correctly in BOTH writing modes because it uses cqi, not cqw */
.card h2 {
  font-size: clamp(1.1rem, 6cqi, 2rem);
}

If you had written 6cqw, the heading would scale with physical width even after the text rotated to flow vertically, breaking the relationship between type size and the line it sits on. Using the logical cqi/cqb pair keeps fluid sizing correct for internationalized layouts without writing a second rule.

A related extension is to promote the fluid values to custom properties so a whole component scales from one declaration. Because the units are plain lengths, a token can hold a clamp() expression built on cqi and every consumer inherits the fluid behaviour:

.card {
  --step-0: clamp(0.95rem, 1.4cqi + 0.7rem, 1.15rem);
  --step-2: clamp(1.4rem, 5cqi, 2.25rem);
  --space-m: clamp(0.75rem, 3cqi, 1.5rem);

  padding: var(--space-m);
  gap: var(--space-m);
  font-size: var(--step-0);
}

.card h2 { font-size: var(--step-2); }

One caution: a custom property containing cqi resolves against the nearest query container of the element that uses it, not the element that declared it. Inheriting a cq* token across a container boundary therefore changes its value, which is usually what you want and occasionally a surprise. The same bounded-scale thinking applied to whitespace is covered in the guide to building a fluid space scale with clamp().

Accessibility: keep a font-relative term in the expression

A clamp() whose preferred value is pure cqiclamp(1rem, 5cqi, 2rem) — ignores the user's font size entirely between the two bounds, because container units do not scale with the root font size. A reader who raises their default text size sees the surrounding UI grow while this heading stays put. Adding a rem term, as in clamp(1rem, 3cqi + 0.6rem, 2rem), restores that relationship: the container still drives most of the variation, but the whole curve shifts up when the user asks for larger text. Do this for anything that is read rather than looked at, and verify it by changing the browser's default font size rather than by zooming, since zoom scales container units too and hides the problem.

Browser support note

Container query length units shipped alongside size container queries and are part of the same baseline: Chrome and Edge 105+, Safari 16.0+, and Firefox 110+ all resolve cqw, cqh, cqi, cqb, cqmin, and cqmax. There is no separate flag for the units once size queries are supported. For older engines the units degrade to their viewport equivalents only when no ancestor is a query container; in practice you should still ship a static fallback value first so unsupported browsers render a fixed size, as covered in the guide to feature detection with @supports.

FAQ

What is the difference between cqw and cqi?cqw is always 1% of the container's physical width, while cqi is 1% of its inline size. In a standard horizontal writing mode they resolve to the same value, but cqi follows the writing direction, so it maps to height under vertical writing modes. Prefer cqi for logical, direction-agnostic layouts.

Why does my cqi value resolve to zero? Container query units only resolve against an ancestor that declares container-type. If no ancestor is a query container, the unit falls back to the small-viewport equivalent, and if the queried axis has no definite size the unit computes to zero. Set container-type: inline-size on the parent.

Can I use cqi for font sizing directly? Yes, but wrap it in clamp() so the text stays within a readable minimum and maximum. A bare cqi font size scales without limit and becomes unreadable in very wide or very narrow containers.

Do container query units work without an @container rule? Yes. The length units resolve from the container's measured size independently of any @container block, so you can use cqi for fluid sizing even when you write no conditional queries at all.

Related articles

More pages in the same section.