CSS Clamp

Deep dive

The maths and mechanics behind clamp()

Where the numbers in a fluid value come from, how the browser resolves them, and what to watch out for with zoom, units and design tokens.

How the browser resolves clamp()

The spec defines clamp() as shorthand for a nested min() and max():

clamp(MIN, VAL, MAX) = max(MIN, min(VAL, MAX))

The preferred value is capped by the maximum first, and the result is then raised to the minimum. That order matters in one edge case: if the minimum is larger than the maximum, the minimum wins. Nothing breaks, but the value never moves.

All three arguments can be any length, percentage or number that makes sense for the property, and each one may itself be a calculation. That is why the preferred value can mix units, like -0.75rem + 7.292vw, without wrapping it in calc().

Drawing a straight line

A fluid value is a straight line between two points: the minimum size at the minimum viewport, and the maximum size at the maximum viewport. Written as a formula, it is the classic y = mx + b:

slope     = (maxSize - minSize) / (maxViewport - minViewport)
intercept = minSize - slope × minViewport
preferred = intercept + slope × 100vw

The slope is how much the value grows per pixel of screen width. Because 1vw is 1% of the viewport width, the slope is multiplied by 100 to turn it into vw. The intercept moves the line up or down so it passes exactly through the starting point.

A worked example

Take the generator's defaults, all in rem:

  • Size: 1rem to 8rem
  • Viewport: 24rem (384px) to 120rem (1920px)
slope     = (8 - 1) / (120 - 24) = 7 / 96 ≈ 0.07292
intercept = 1 - 0.07292 × 24 = -0.75rem
preferred = -0.75rem + 7.292vw

result    = clamp(1rem, -0.75rem + 7.292vw, 8rem)

You can check both ends by filling in the viewport width:

  • At 384px: -12px + 7.292% × 384px = 16px, exactly 1rem.
  • At 1920px: -12px + 7.292% × 1920px = 128px, exactly 8rem.

Outside that range the line keeps going, but the minimum and maximum cut it off. That is the whole trick.

Why the intercept uses rem

You could write a fluid value with only viewport units, like font-size: 4vw. Please don't. When someone zooms in, the page gets fewer CSS pixels wide, so 4vw actually gets smaller. Their zoom is working against the text.

Mixing units fixes most of that. The rem part of the preferred value grows with zoom and with the browser's font size setting, and the rem minimum and maximum do too. WCAG success criterion 1.4.4 asks that text can be resized to 200%, so for text it is worth testing at 200% zoom. A common rule of thumb is to keep the maximum within about 2.5 times the minimum; bigger ranges are more likely to stop growing with zoom before reaching 200%.

Viewport units and container units

vw ties a value to the whole browser window. That is right for page padding and headings, but not for a card that can sit in a narrow sidebar or a wide main column.

Container query units solve this. cqi (or cqw) is 1% of the width of the nearest ancestor with container-type: inline-size. Swap vw for cqi and the same formula scales with the component instead of the window:

.card-list {
    container-type: inline-size;
}

.card {
    padding: clamp(1rem, -0.75rem + 7.292cqi, 8rem);
}

The generator's preview works exactly like this: the emulated screen is a container, so it can show what a value looks like at 1152px while your window is a different size.

Clamp in Tailwind CSS

For a one-off value, use an arbitrary value. Leave out the spaces:

<section class="px-[clamp(1rem,-0.75rem+7.292vw,8rem)]">

If you use a value more than once, make it a design token. In Tailwind CSS v4 you define it in your stylesheet and get a utility for free:

@theme {
    --spacing-section: clamp(1rem, -0.75rem + 7.292vw, 8rem);
    --text-display: clamp(2.5rem, 1.875rem + 2.604vw, 5rem);
}
<section class="px-section">
    <h1 class="text-display">Fluid by default</h1>
</section>

This site does the same: its whole type scale is a set of clamp values.

Performance and support

clamp() is cheap. The browser only recalculates it when the viewport or container changes size, and it has no runtime cost of its own beyond normal layout. It is supported in every current browser, and has been since 2020.

Summary

  • A fluid value is a straight line between two points, cut off at both ends.
  • Slope = size range ÷ viewport range. Intercept = the start size minus slope × start viewport.
  • Use rem for the bounds and the intercept, and test text at 200% zoom.
  • Use cqi instead of vw when a component should scale with its container.

Ready to try it? Open the generator, or start with the guide if you skipped it.