A design gives you two facts: a heading should be 16px on a 320px screen and 32px on a 1440px screen. The intent is clear. The implementation is not. A breakpoint can jump from one size to the other, and a hand-written value such as clamp(1rem, 2.5vw, 2rem) may look plausible without ever passing through both design targets.

The useful version of fluid sizing is not guesswork. Two viewport/value pairs define a straight line, and that line can be expressed as a CSS viewport term with fixed bounds.

Open the CSS Clamp Calculator →

This guide derives the formula, shows how to validate it, covers forward and reverse ranges, explains when rem or px is the better output, and provides small generators in JavaScript, Python, and Bash. The goal is CSS you can explain in review, not a mysterious number copied from a calculator.

What clamp() actually does

CSS clamp() accepts three comma-separated calculations:

property: clamp(minimum, preferred, maximum);

The browser evaluates the preferred expression and keeps the result between the lower and upper bounds. Formally, the result is equivalent to:

max(minimum, min(preferred, maximum))

That definition matters because the middle expression is not a “default.” It is the responsive rule the browser evaluates at the current viewport. The bounds only take over when that rule would leave the permitted range.

For example:

font-size: clamp(1rem, 0.7143rem + 1.4286vw, 2rem);

behaves in three regions:

  1. At narrow widths, the preferred result would be less than 1rem, so the minimum wins.
  2. Between the selected endpoints, the middle expression changes linearly with the viewport.
  3. At wide widths, the preferred result would exceed 2rem, so the maximum wins.

clamp() is documented by MDN as widely available across modern browsers. The formal comparison-function behavior lives in CSS Values and Units Level 4.

Why clamp(1rem, 2.5vw, 2rem) is usually incomplete

A simple viewport term can be useful, but it has only a slope. It has no independently chosen intercept. That means it normally cannot pass through two arbitrary design targets.

Take this popular-looking declaration:

font-size: clamp(1rem, 2.5vw, 2rem);

At a 320px viewport, 2.5vw is 8px, so the minimum clamps it to 16px. At 1440px, 2.5vw is 36px, so the maximum clamps it to 32px. It reaches the right bounds eventually, but its fluid region runs from 640px to 1280px—not from 320px to 1440px. At 880px it produces 22px, while a straight interpolation between the design endpoints should produce 24px.

The difference is the intercept. To hit both endpoints exactly, the preferred expression usually needs a fixed term plus a viewport term:

fixed-intercept + viewport-slope

This is the same y = mx + b equation used for any straight line.

Deriving the formula from two endpoints

Define four inputs:

  • v₁: minimum viewport width
  • v₂: maximum viewport width
  • s₁: value at the minimum viewport
  • s₂: value at the maximum viewport

The slope is the change in value divided by the change in viewport:

m = (s₂ - s₁) / (v₂ - v₁)

The intercept is the value the line would have at a zero-width viewport:

b = s₁ - m × v₁

CSS defines 1vw as one percent of the viewport width, so a slope measured per pixel becomes a vw coefficient by multiplying by 100:

preferred = b + (m × 100)vw

Worked example: 16px to 32px from 320px to 1440px

Start with the slope:

m = (32 - 16) / (1440 - 320)
  = 16 / 1120
  = 0.0142857143

Then calculate the intercept:

b = 16 - 0.0142857143 × 320
  = 11.4285714px

Convert the slope to vw:

m × 100 = 1.42857143

The px result is therefore:

font-size: clamp(16px, 11.4286px + 1.4286vw, 32px);

If the root font size is 16px, divide the fixed lengths by 16. The vw coefficient does not change:

font-size: clamp(1rem, 0.7143rem + 1.4286vw, 2rem);

Verify both endpoints

At 320px, one vw is 3.2px:

11.4286px + 1.4286 × 3.2px ≈ 16px

At 1440px, one vw is 14.4px:

11.4286px + 1.4286 × 14.4px ≈ 32px

This endpoint check catches most hand-calculation mistakes. It also explains why rounding should happen at the end. If you round the slope before calculating the intercept, the accumulated error can move one endpoint far enough to be visible.

A practical calculator workflow

The ZeroTool CSS Clamp Calculator keeps the inputs close to the design handoff:

  1. Choose font-size, padding, margin, gap, width, or a custom property.
  2. Enter the minimum and maximum viewport widths in pixels.
  3. Enter the value required at each viewport endpoint.
  4. Choose px or rem output and, for rem, confirm the root font size.
  5. Inspect the five sampled values and move the preview slider.
  6. Copy either the complete CSS declaration or only the generated value.

The curve is intentionally linear because two points determine one line. The sample table exposes the midpoint and quarter points, making it easier to spot a technically correct curve that still feels wrong for the design.

The calculator also handles three cases that simple snippets often miss:

  • Reverse ranges: the value decreases as the viewport gets wider.
  • Constant ranges: equal endpoint values simplify to one fixed length.
  • Negative values: accepted for margin and custom properties, where negative lengths can be intentional.

All calculation and persistence happen in the browser. Reset restores the defaults and removes the saved form entry.

Choosing rem or px output

The formula can use mixed units. That is not a workaround; CSS math functions are designed to combine compatible lengths.

Use rem for text and user-scalable systems

With rem output, the minimum, maximum, and fixed intercept are relative to the root font size:

.hero-title {
  font-size: clamp(1rem, 0.7143rem + 1.4286vw, 2rem);
}

This keeps part of the expression connected to the user’s font-size preferences. It is usually the better starting point for typography and for spacing tokens intended to scale with the type system.

The conversion still needs an explicit root size. If your project sets html { font-size: 62.5%; }, a calculator configured for 16px will produce the wrong rem values for that codebase. Match the calculator’s root input to the value your design system actually assumes.

Use px when the endpoints are truly fixed measurements

Pixel output is useful for a visual dimension that should land on exact CSS-pixel endpoints:

.content-shell {
  padding-inline: clamp(20px, 12px + 2.5vw, 48px);
}

CSS pixels are not physical device pixels, but they are still fixed lengths within CSS layout. For text, that fixed relationship can be less friendly to user font preferences than rem.

The vw term remains viewport-relative

Selecting rem does not turn the entire expression into a font-relative value. The middle expression still includes vw, which responds to viewport width. That is why zoom and text-resize testing remains necessary: the fixed and viewport-relative parts do not react identically to every user setting.

Use the CSS Unit Converter when you need to audit the endpoint conversion separately.

Reverse fluid scales

Sometimes a value should shrink on wider screens. A compact mobile card may need more vertical padding for touch comfort, while a dense desktop layout needs less. Entering 32px at 320px and 16px at 1440px gives a negative slope:

padding-block: clamp(1rem, 2.2857rem - 1.4286vw, 2rem);

At the smaller viewport the preferred expression evaluates to 2rem; at the larger viewport it evaluates to 1rem.

There is a subtle but important implementation detail: CSS still expects the lower bound first and the upper bound last. Reversing the direction of the scale does not mean emitting clamp(2rem, ..., 1rem). The bounds stay sorted as 1rem and 2rem; the negative vw coefficient carries the direction.

The CSS specification says the minimum wins when authors provide conflicting bounds. Sorting them avoids that trap and makes the expression behave as intended.

Constant values are a valid result

If both endpoint values are 16px, the slope is zero. The long form would technically be possible:

font-size: clamp(1rem, 1rem + 0vw, 1rem);

but it communicates nothing useful. The smallest correct result is:

font-size: 1rem;

A calculator should simplify this case rather than pretending every pair of inputs needs a responsive expression. The same principle applies in a design system: use fluid tokens only where the interpolation serves a real layout purpose.

Fluid type is not automatically accessible type

Fluid typography solves visual interpolation. It does not, by itself, satisfy an accessibility requirement.

WCAG 2.2 Success Criterion 1.4.4 requires text to be resizable up to 200% without loss of content or functionality, apart from stated exceptions. MDN’s clamp() accessibility guidance recommends a relative maximum that is at least twice the minimum as one way to avoid prematurely capping text growth.

That ratio is a useful warning, not a compliance certificate. The complete test is behavioral:

  1. Zoom the page to 200%.
  2. Confirm text is readable and no control disappears.
  3. Check that labels wrap instead of clipping.
  4. Verify fixed-height containers do not cut off content.
  5. Test the narrow layout again with the larger text.

The W3C G179 technique focuses on the same outcome: content and functionality must remain available when text grows.

For a font-size range narrower than two-to-one, the ZeroTool calculator displays an informational notice. It does not block the CSS because valid product constraints differ, and only testing the real component can determine whether the result works.

Common mistakes and why they happen

1. Treating the middle value as an average

The preferred value is an expression, not the midpoint between the minimum and maximum. A fixed middle value such as clamp(1rem, 1.5rem, 2rem) never responds to viewport width; it always resolves to 1.5rem.

2. Using a bare vw value without checking endpoints

2vw may look good at one screenshot width and miss the design everywhere else. Calculate the intercept or verify both endpoints before shipping.

3. Rounding the slope too early

The displayed CSS can use four decimal places, but the calculation should keep full precision until formatting. Early rounding shifts the intercept and makes endpoint checks fail.

4. Omitting spaces around + or -

CSS math syntax requires whitespace around binary addition and subtraction. Write:

clamp(1rem, 0.7143rem + 1.4286vw, 2rem)

not:

clamp(1rem, 0.7143rem+1.4286vw, 2rem)

5. Making the viewport interval zero

If the minimum and maximum viewport widths match, the slope divides by zero. Decide whether you intended a fixed value or enter two distinct viewport endpoints.

6. Assuming a 16px root everywhere

Sixteen pixels is a common default, not a universal project contract. Check the root styles and any user-facing font settings before converting px endpoints to rem.

7. Using viewport interpolation for a container-sized component

A card can be narrow inside a wide desktop viewport. A vw-based value responds to the page, not the card. For independently reusable components, consider whether container queries and container-relative units are a better hand-written design. The calculator deliberately generates viewport-based CSS.

8. Applying fluid values to every token

Borders, icon strokes, baseline grids, and other exact details often benefit from stable values. Fluidity is a design decision, not a default refactor.

Build a small type and spacing system

Once the endpoint pairs are approved, store the results as custom properties rather than scattering formulas across components:

:root {
  --font-body: clamp(1rem, 0.9643rem + 0.1786vw, 1.125rem);
  --font-title: clamp(2rem, 1.5714rem + 2.1429vw, 3.5rem);
  --space-section: clamp(3rem, 2.1429rem + 4.2857vw, 6rem);
  --space-card: clamp(1rem, 0.8571rem + 0.7143vw, 1.5rem);
}

body {
  font-size: var(--font-body);
}

h1 {
  font-size: var(--font-title);
}

.section {
  padding-block: var(--space-section);
}

Name tokens by role, record the endpoint assumptions in your design-system documentation, and keep the generated values centralized. The CSS Variables Generator can help assemble the final :root block once the formulas are settled.

Avoid generating an entire modular type scale from one pair without design review. Linear interpolation answers “how does this one token move between two points?” It does not decide whether every heading level should share the same ratio or endpoints.

Generate clamp values in JavaScript

The browser calculator is fastest for one-off work. For build tooling or token pipelines, the same calculation is small enough to keep dependency-free:

function clampFromPoints({
  minViewport,
  maxViewport,
  minValue,
  maxValue,
  unit = 'rem',
  root = 16,
}) {
  if (!(maxViewport > minViewport)) {
    throw new Error('maxViewport must be greater than minViewport');
  }
  if (unit === 'rem' && !(root > 0)) {
    throw new Error('root must be greater than zero');
  }

  const round = value => Number(value.toFixed(4));
  const length = px =>
    unit === 'rem' ? `${round(px / root)}rem` : `${round(px)}px`;

  if (Math.abs(maxValue - minValue) < 1e-9) {
    return length(minValue);
  }

  const slope = (maxValue - minValue) / (maxViewport - minViewport);
  const interceptPx = minValue - slope * minViewport;
  const intercept = unit === 'rem' ? interceptPx / root : interceptPx;
  const vw = slope * 100;
  const sign = vw < 0 ? '-' : '+';
  const lower = Math.min(minValue, maxValue);
  const upper = Math.max(minValue, maxValue);

  return `clamp(${length(lower)}, ${round(intercept)}${unit} ${sign} ${round(Math.abs(vw))}vw, ${length(upper)})`;
}

console.log(clampFromPoints({
  minViewport: 320,
  maxViewport: 1440,
  minValue: 16,
  maxValue: 32,
}));
// clamp(1rem, 0.7143rem + 1.4286vw, 2rem)

Keep the unrounded numbers for calculations and round only when serializing CSS. If your token pipeline accepts decimals as strings, add tests for the two endpoints, a reverse range, and a constant range.

Generate the same value in Python

The Python version is useful in design-token scripts or static-site build steps:

def clamp_from_points(v_min, v_max, s_min, s_max, root=16):
    if v_max <= v_min:
        raise ValueError("v_max must be greater than v_min")
    if root <= 0:
        raise ValueError("root must be greater than zero")

    if abs(s_max - s_min) < 1e-9:
        return f"{s_min / root:.4f}".rstrip("0").rstrip(".") + "rem"

    slope = (s_max - s_min) / (v_max - v_min)
    intercept_rem = (s_min - slope * v_min) / root
    vw = slope * 100
    lower, upper = sorted((s_min, s_max))
    sign = "-" if vw < 0 else "+"

    return (
        f"clamp({lower / root:.4f}rem, "
        f"{intercept_rem:.4f}rem {sign} {abs(vw):.4f}vw, "
        f"{upper / root:.4f}rem)"
    )


print(clamp_from_points(320, 1440, 16, 32))

For production formatting, normalize trailing zeros consistently. The arithmetic is the important part; formatting policy should match the rest of your generated CSS.

A Bash/awk check for CI

You may not want a language runtime in a small CI check. This shell function prints a px-based value using awk:

clamp_px() {
  awk -v v1="$1" -v v2="$2" -v s1="$3" -v s2="$4" 'BEGIN {
    if (v2 <= v1) exit 2
    if (s1 == s2) {
      printf "%.4gpx\n", s1
      exit
    }
    m = (s2 - s1) / (v2 - v1)
    b = s1 - m * v1
    lo = s1 < s2 ? s1 : s2
    hi = s1 > s2 ? s1 : s2
    printf "clamp(%.4gpx, %.4gpx %s %.4gvw, %.4gpx)\n", \
      lo, b, m < 0 ? "-" : "+", (m < 0 ? -m : m) * 100, hi
  }'
}

clamp_px 320 1440 16 32

This is intentionally a validation helper, not a full token compiler. Once generation requires multiple units, schema validation, or file output, a tested JavaScript or Python script is easier to maintain.

Test the CSS before you merge it

A short checklist catches both formula and layout failures:

  • Endpoint check: inspect the computed value at the minimum and maximum viewport widths.
  • Midpoint check: confirm the center value matches the intended interpolation.
  • Outside-range check: test narrower and wider viewports to verify the bounds hold.
  • Zoom check: test at 200% browser zoom with real content.
  • Localization check: use the longest supported language strings, not lorem ipsum.
  • Container check: ensure text can wrap and cards can grow vertically.
  • Token check: verify the root font-size assumption matches the project.
  • Syntax check: keep spaces around + and - in the preferred expression.

The calculator’s curve and five-row sample table cover the numerical checks. Browser DevTools and your component test suite must cover the layout checks.

The simplest rule that works

Use two real endpoints, derive one line, keep the bounds sorted, and test the result in the component that consumes it. That is the whole method.

clamp() is valuable because it removes unnecessary breakpoint jumps, not because every CSS length should become a formula. A precise fluid value should be easier to reason about than the media queries it replaces. If the result needs a paragraph of exceptions, the layout probably wants explicit breakpoints instead.

Generate a CSS clamp() value from your endpoints →

Further reading