codr.tools
Tools

CSS clamp() calculator

Work out a fluid clamp() declaration from a size at two viewport widths, with the slope and intercept shown, a live preview at any width, a type scale in custom properties and a check that the text still resizes.

Sizes and viewport widths

Those two sizes are in
Unit of the minimum and maximum size

Browsers default to 16. Only change it if you changed the font size on html.

Write the declaration in
Unit of the generated declaration

rem keeps both ends resizable by the reader.

Live preview

viewport 768pxfont-size 19.73px= 1.2333rem

Fluid type grows with the viewport and stops at both ends. Drag the slider to see it move.

The size is computed in JavaScript from the generated declaration, so the sample matches a real browser at that viewport width rather than at this page's width.

Generated CSS

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

How the preferred value was worked out

Slope
(24 - 16) / (1280 - 320) = 0.008333 px of size per px of viewport
Slope in vw
0.008333 * 100 = 0.833333vw
Intercept
16 - 0.008333 * 320 = 13.333333px
Intercept in rem
13.333333 / 16 = 0.833333rem
Check
at 320px: 0.8333 + 0.8333 * 3.2 = 3.49986rem

Text size check. At 768px wide, doubling the reader's default font size to 32px takes this value from 19.73px to 33.07px. It answers to the reader, which is what WCAG 2.2 SC 1.4.4 asks for.

Size across the viewport range

Computed font-size against viewport width: flat at 16 pixels below 320 pixels wide, a straight slope to 24 pixels at 1280 pixels wide, then flat again.01429320px1280pxpx

The line is flat below 320px and above 1280px, and straight between them. The ring marks the slider's width. The table below gives the same numbers.

Computed font-size at common viewport widths
ViewportComputedIn remPart of the curve
320px16px1remfloor
375px16.46px1.029remgrowing
414px16.78px1.049remgrowing
768px19.73px1.233remgrowing
1024px21.87px1.367remgrowing
1280px24px1.5remceiling
1440px24px1.5remceiling
1920px24px1.5remceiling

Type scale

Turn this on to multiply the size above through a ratio and get one fluid custom property per step, ready to paste into :root.

Work out a fluid clamp() declaration from a size at two viewport widths, with the slope and intercept shown, a live preview at any width, a type scale in custom properties and a check that the text still resizes.

How to use this tool#

  1. Pick the property. font-size is the usual one, but the same arithmetic sizes padding, margin, gap and width. The preview changes to suit.
  2. Enter the two sizes you want: the size on the smallest screen you design for, and the size on the largest. Both are read in the unit you choose below them.
  3. Enter the two viewport widths those sizes belong to, in pixels. Between them the size moves in a straight line. Outside them it stops.
  4. Leave the root font size at 16 unless you set a font size on thehtml element yourself. It is only used to convert between px and rem.
  5. Drag the slider to see the computed size at any width. The number is worked out in JavaScript from the generated declaration, so it shows what a browser at that width would do, not what this page is doing.
  6. Copy the declaration, and read the notes under it. They cover the cases that bite: a negative rem term, a value that cannot be resized, a size that shrinks as the screen grows.

Nothing is sent anywhere. The page is static files and the calculation runs in your browser.

What this does#

A fluid size is one value that grows with the screen instead of jumping at breakpoints. You pick two points, say 16px on a 320px-wide phone and 24px on a 1280px-wide laptop, and the browser draws the straight line between them. clamp() is what holds that line still at both ends, so the type never shrinks below the small size on a watch or grows past the large size on a television.

The hard part is the middle argument. It has to be the equation of that line in CSS units, and getting it by hand means two divisions and a subtraction each time you change your mind. This page does the arithmetic, shows it so you can check it, and previews the result at any width.

What comes out
h1 {
  font-size: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem);
}

How clamp() works#

clamp() takes three calculations: a minimum, a preferred value and a maximum. The CSS Values and Units Module Level 4 defines it as representing "its central calculation, clamped according to its min and max calculations, favoring the min calculation if it conflicts with the max", and then spells the equivalence out: "given clamp(MIN, VAL, MAX), it represents exactly the same value as max(MIN, min(VAL, MAX))" (CSS Values 4, section 10.2).

clamp() is a nested max and min
/* The same computed value, written the long way. */
font-size: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem);
font-size: max(1rem, min(0.8333rem + 0.8333vw, 1.5rem));

Two things follow from that definition. First, the minimum wins any argument with the maximum: if you write a MIN larger than your MAX, the inner min() returns MAX, the outermax() throws it away, and the value is pinned to MIN forever. That is why this page always writes the smaller of your two sizes first, even when the size is meant to shrink as the screen grows. Second, each of the three arguments is a calculation in its own right, so a sum like 0.8333rem + 0.8333vw goes straight inside without a calc()wrapper.

The spec also allows none in place of a bound: clamp(1rem, VAL, none)is a floor with no ceiling, and clamp(none, VAL, 3rem) is a ceiling with no floor. That part is newer than the function itself, so check support before relying on it.

Where the preferred value comes from#

The preferred value is the equation of a straight line through two points, written in the form CSS can evaluate: a constant plus a share of the viewport width. 1vw is one percent of the viewport width, so a coefficient in vw is just the slope multiplied by 100.

  • Slope is the rise over the run: the difference between the two sizes divided by the difference between the two viewport widths, both in pixels.
  • Intercept is what is left when you take the sloped part away at the small end: the minimum size minus the slope times the minimum viewport width. It is the value the line would have at a viewport width of zero, which is why it is often smaller than either of your sizes, and sometimes negative.

Take 16px at 320px growing to 24px at 1280px, the example the tool starts with:

The arithmetic, checked at three widths
slope     = (24px - 16px) / (1280px - 320px) = 0.008333
in vw     = 0.008333 * 100                      = 0.8333vw
intercept = 16px - 0.008333 * 320px             = 13.3333px
in rem    = 13.3333px / 16                      = 0.8333rem

at  320px: 13.3328 +  2.6666 = 15.999px
at  800px: 13.3328 +  6.6664 = 19.999px
at 1280px: 13.3328 + 10.6662 = 23.999px

Dividing the intercept by the root font size turns it into rem, which is what the tool emits by default: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem). The three check lines run the finished declaration back through the arithmetic at three widths, which is the quickest way to satisfy yourself that a preferred value is right.

Rounding is to four decimals in rem and three in px, and the check lines show what it costs. A ten-thousandth of a rem is a six-hundredth of a pixel, and a ten-thousandth of avw on a 1920px screen is two thousandths of a pixel. At the bottom of the range the rounded preferred value lands under a thousandth of a pixel below 16px, and the clamp floor takes it straight back up.

vw, vi and container query units#

vw is one percent of the viewport's width. Two details about it are worth knowing before you use it for type.

It is the large viewport.CSS Values 4 defines vw, svw, lvw and dvw in one list, each "equal to 1% of the width of the large viewport size, small viewport size, large viewport size, and dynamic viewport size, respectively". So vw is the same length aslvw: on a phone, the viewport with the browser's own chrome retracted. Avw length does not change as the address bar slides away. The small, large and dynamic variants arrived in Firefox 101, Chrome 108 and Safari 15.4.

It ignores the scrollbar. The spec is explicit: outside the case where the root element is set to scroll unconditionally, "the viewport-percentage lengths are sized assuming that scrollbars do not exist" (CSS Values 4, section 6.1.2). That is the whole story behind the classic width: 100vw bug: on a desktop browser with a classic scrollbar, the element is as wide as the viewport including the scrollbar, so the page scrolls sideways. For font sizes the effect is a rounding error rather than a bug, but it explains why a vw value is never quite what you measured in devtools.

vi is the logical version: one percent of the initial containing block along the root element's inline axis. In a left-to-right, top-to-bottom document it is the same asvw, and in a vertical writing mode it follows the text instead of the screen. It shipped in Firefox 101, Chrome 108 and Safari 15.4.

Container query units measure the component instead of the screen.cqi is one percent of the query container's inline size, cqb one percent of its block size, with cqw, cqh, cqmin andcqmax beside them. They need an ancestor with container-type set: when "no eligible query container is available, then use the small viewport size for that axis" (CSS Conditional 5, section 7), so a cqi length without a container quietly becomes a viewport length rather than failing. Support is Safari 16, Chrome 105 and Firefox 110.

Same shape, different ruler
/* vw: a share of the viewport, whatever the component sits in. */
.card h2 {
  font-size: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem);
}

/* cqi: a share of the container's inline size, so the same component
   sets smaller type in a narrow column than in a wide one. */
.card {
  container-type: inline-size;
}

.card h2 {
  font-size: clamp(1rem, 0.5rem + 2cqi, 1.5rem);
}

Use vw for page-level type, where the heading should answer to the screen. Reach forcqi when the same component appears in a sidebar and in a main column and should set different type in each. The arithmetic on this page works for both: swap the unit in the output and read the viewport widths as container widths.

The zoom problem, and what fixes it#

WCAG 2.2 success criterion 1.4.4 Resize Text, at level AA, reads: "Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality" (Understanding 1.4.4). Its understanding document lists several ways a reader does that: page zoom, magnification, text-only resizing, and setting a font size in the browser.

Text sized purely in viewport units answers to none of them. W3C failure technique F94, "Failure of Success Criterion 1.4.4 due to incorrect use of viewport units to resize text", puts it in one sentence: "As these units are relative to the viewport, it means they cannot be resized by zooming or adjusting text-size" (F94). A heading set to 5vw is five percent of the viewport before the reader touches anything and five percent of the viewport afterwards.

The text size check under the generated CSS makes it concrete. It computes the size twice, once at your root font size and once at double it, and tells you whether the value moved. A declaration like clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem) moves, because both bounds and part of the preferred value are in rem. clamp(16px, 13.333px + 0.833vw, 24px) does not move at all.

Two smaller points fall out of the same arithmetic. A steep line across a narrow range of viewport widths gives a negative rem term, which means raising the root font size pulls the preferred value down; the rem floor still catches it, but the middle of the range gets worse rather than better, and the tool flags it. And a preferred value with no rem term at all, which happens when both of your points sit on a line through the origin, is purevw between the two widths even though the bounds outside them are fine.

Fluid type is not an accessibility problem by itself. Fluid type with nothing in it that the reader controls is. While you are checking type, thecolour contrast checker covers the other half of readable text.

Fluid spacing, not just type#

Padding, margin, gap, width, border-radius and any other length take the same treatment, and spacing benefits more than type does: the gap that looks generous on a phone is usually mean on a desktop. Nothing in the arithmetic changes. Only the property name does.

A two-step spacing scale
:root {
  /* 12px to 20px, and 24px to 40px, both from 320px to 1280px wide. */
  --space-s: clamp(0.75rem, 0.5833rem + 0.8333vw, 1.25rem);
  --space-m: clamp(1.5rem, 1.1667rem + 1.6667vw, 2.5rem);
}

.card {
  padding: var(--space-m);
  gap: var(--space-s);
}

Custom properties are the practical way to use this. Name a handful of steps, define each one as a clamp, and use the names everywhere. The type scale mode in the tool generates exactly that block for a whole set of font sizes, with a ratio you can tighten at the small end so headings do not tower over a phone.

One caution for spacing: of these properties, only margin takes a negative length. Givepadding or gap a negative value and the whole declaration is thrown away, which with a fluid value can happen at one end of the range and not the other.

Browser support#

clamp(), with min() and max(), is in every current browser: Chrome 79 (December 2019), Edge 79 (January 2020), Safari 13.1 (March 2020) and Firefox 75 (April 2020).MDN marks it Baseline widely available and dates cross-browser availability to July 2020. There is no vendor prefix and no polyfill worth carrying.

A browser that does not understand the function drops the whole declaration, which is the ordinary CSS cascade rule rather than anything special about clamp(). If you still support something ancient, put a plain value on the line above and let the newer one overwrite it.

Fallback, if you need one
h1 {
  font-size: 1.5rem;                                  /* ignored where the next line parses */
  font-size: clamp(1rem, 0.8333rem + 0.8333vw, 1.5rem);
}

Gotchas#

  • Equal viewport widths. If the two widths are the same, the slope is a division by zero and there is no line to draw. The tool refuses the input rather than emittingNaN into your stylesheet.
  • A minimum above the maximum. Valid CSS, and a perfectly reasonable thing to want: a hero heading that is huge on a phone and calmer on a desktop. Because MIN beats MAX, the two bounds have to be swapped in the output, and the slope comes out negative. The tool does that for you and says so.
  • em bounds in a font-size clamp. MDN is clear that whenem is used on the font-size property itself it means the inherited font size, so clamp(1em, 4vw, 3em) resolves its bounds against the parent, not against the element or the root. Nest two of those and the sizes compound. rem has no such behaviour.
  • Mixing types. The three arguments have to be the same kind of value. A unitless number cannot be added to a length, so there is no way to write a fluidline-height as a bare ratio; size it in rem, or leave it unitless and let it follow the font size it is on.
  • Testing at one width. A fluid value looks right at whatever width you happen to have the window at. Check both flat ends as well as the middle, which is what the table and the slider on this page are for.
  • Too wide a range. A single clamp from 320px to 1920px gives a very gentle slope and a large intercept, and the type spends most of its life near the middle. Two or three narrower ranges, or a smaller maximum viewport width such as 1280px, usually read better.

Questions people ask#

Should the preferred value be pure vw or rem plus vw?

rem plus vw, for anything a person reads. It is the difference between text that responds to the reader's font size and text that does not, as F94 sets out. The rem term also lets you keep the bounds in rem, so the flat ends of the range scale fully. Pure vw is fine for decorative lengths that no one has to read.

Which viewport widths should I pick?

Use the real ends of your design rather than device names. 320px is the narrowest screen still in common use and makes a safe lower anchor; the upper one is usually the maximum width of your content column, or the width past which the design stops changing, often somewhere between 1200px and 1600px. Above the upper anchor the size is flat, which is normally what you want on a very wide monitor.

What happens outside the two widths?

Nothing moves. Below the minimum viewport width the value sits at the minimum size, above the maximum viewport width it sits at the maximum size. That is the entire point of wrapping the line in clamp(): the same equation without the bounds would keep shrinking to nothing on a small screen and keep growing forever on a large one.

Can I use clamp() with CSS custom properties?

Yes, in both directions. A custom property can hold a whole clamp() value, which is how the type scale block above is built, and a clamp() can read its arguments from custom properties. A property used inside a calculation has to resolve to a valid value at the point it is used, so keep units in the value rather than assembling them from fragments.

Does this work for anything other than lengths?

clamp() is a general maths function and works wherever a number, length, percentage, angle, time or frequency is allowed, as long as all three arguments are the same kind. This tool is built for lengths, since that covers font sizes and spacing, which is what almost everyone wants it for.

Is the generated CSS sent anywhere?

No. The whole calculation runs in your browser. This site is static files with no backend, so there is nowhere for your numbers to go, and the tool keeps working with the network disconnected.

  • Build a CSS Grid layout visually: set the columns, rows, track sizes and gaps, drag items across cells to place them, and copy the generated CSS and the matching HTML.

  • Build a flex container by eye and copy the code: direction, wrapping, justify-content, align-items, align-content and gap, plus flex-grow, flex-shrink, flex-basis, align-self and order on any item, with only the properties you changed in the output.

  • Draw a CSS easing curve by dragging its two control points, compare it against linear in a live preview, start from the keyword and named presets, and copy the cubic-bezier() value or a ready transition line.

  • Check a foreground and background pair against the WCAG 2 contrast thresholds, composite transparency the way a browser does, find the nearest passing colour, and build a tint, shade and harmony palette with the contrast of every swatch.