Reference

Units in Math Functions

Where units stop being numbers and start being sentences.

Knowing every unit is half the story. The other half is combining them. A modern layout rarely uses a unit in isolation; it mixes them: “this big, but never smaller than that, and no larger than this.” That’s what CSS math functions are for, and they’re where units stop being a glossary and start being a system.

calc()                     mix units and do arithmetic
min() max()                take the smallest / largest value
clamp()                    lock a value between a floor and a ceiling
round() mod() rem()        snap a value to a step; take a remainder
sin() cos() tan() atan2()  turn angles into numbers, and back
pow() sqrt() hypot() log() exponents, roots, distances
abs() sign()               magnitude and direction
pi  e  infinity  NaN       constants you'd otherwise type out

The first four are the ones that mix units — px with rem, % with vw, em with cqi — resolving them at used-value time. That’s the bridge between every unit family in this guide. The rest of the list is fussier about types, and each section below says how.

Everything above the typed-arithmetic section is cross-browser: the four classics have been universal for a decade, and the rest of the list reached all three engines between 2023 and mid-2025. Each section below states when.


calc()

calc() performs arithmetic, and crucially it can mix units that would otherwise be incompatible:

.sidebar { width: calc(100% - 240px); }
.title   { font-size: calc(1rem + 1vw); }
.box     { margin: calc(1.5em + 4px); }

The browser resolves the mixed terms once it knows the concrete values during layout. A few rules that trip people up:

  • Operators need whitespace. calc(100% - 240px) is valid; calc(100%-240px) is not. The + and - operators must be surrounded by spaces (because -240px is otherwise a single negative number).
  • Multiplying by a number always works; multiplying two lengths produces nothing usable. calc(2 * 1rem) is fine. calc(1rem * 1rem) would be a length², and CSS has no <area> type for any property to consume, so it’s useless rather than helpful. Dividing two lengths, by contrast, cancels the units to a plain <number>, which is usable (see typed arithmetic below).
  • It nests. calc() can contain min(), max(), clamp(), and other calc()s.
Use when
  • Mixing units: calc(100% - 240px), calc(1rem + 1vw)
  • Subtracting a fixed gutter from a flexible width
  • Composing with min()/max()/clamp()
Avoid when
  • Forgetting spaces around + / - — it silently invalidates the value
  • Using fr inside calc() — flexible lengths aren't allowed there
  • Reaching for it where clamp() or a single relative unit already expresses the intent

min() and max()

min() returns the smallest of its comma-separated arguments; max() the largest. Both accept mixed units.

/* Never wider than 65ch, but shrink on small screens */
.article { inline-size: min(100%, 65ch); }

/* At least 44px tall for touch targets, larger if the content wants */
.btn { min-block-size: max(44px, 2.5em); }

The intuition flips for the responsive case: min() sets a ceiling (the value can’t exceed the smallest argument), while max() sets a floor. min(100%, 65ch) means “65ch, but cap at the available width.”

Use when
  • Responsive caps: min(100%, 65ch) for a fluid-but-bounded column
  • Accessibility floors: max(44px, 2.5em) for minimum touch size
  • Mixing relative + absolute to express intent in one line
Avoid when
  • Confusing the directions — min() caps, max() floors
  • Stacking a floor and a ceiling by hand where clamp(min, pref, max) expresses exactly that

clamp()

clamp(MIN, PREFERRED, MAX) is max(MIN, min(PREFERRED, MAX)) in one readable call: a value that scales with PREFERRED but never drops below MIN or rises above MAX. It’s the backbone of fluid typography:clamp() reads like a sentence: never smaller than, ideally, never larger than. The most literary function in CSS.

.headline {
  font-size: clamp(2rem, 5vw + 1rem, 4rem);
}

This is the pattern that appears throughout this guide — in the viewport and container sections especially. The PREFERRED term is where you put your fluid unit (vw, cqi), while the rem floor and ceiling keep it accessible and sane.

Build one below: drag the four terms and the simulated viewport, and watch which of the three arguments is actually deciding the size.

Demo — Build a clamp() and see which term wins

Every unit, explained.

Resolved Winning term At 200% zoom

Use when
  • Fluid type: clamp(2rem, 5vw + 1rem, 4rem)
  • Fluid spacing: clamp(1rem, 4cqi, 2rem) tied to a container
  • Always include a rem term in the preferred value for zoom safety
Avoid when
  • Pure viewport preferred value: clamp(2rem, 8vw, 4rem) still risks zoom failure
  • A cap written in px — it pins the ceiling where the reader's zoom can't lift it
  • Using it for fixed values that never need to scale

round(), mod(), rem()

The stepped-value functions quantize a value: they take a length (or angle, or time) and snap it to a multiple of a step you choose.

round(strategy?, value, step)   snap value to the nearest multiple of step
mod(a, b)                       remainder, sign follows the DIVISOR
rem(a, b)                       remainder, sign follows the DIVIDEND

round() takes four strategies: nearest (the default), up, down, and to-zero:

/* A three-column track that never lands on a fractional pixel */
.col { inline-size: round(down, 100% / 3, 1px); }

/* Snap fluid spacing to the vertical rhythm of one line */
.stack { margin-block-end: round(3.4vh, 1lh); }

That first example is the practical one: fluid math routinely produces 213.333px, and sub-pixel edges are where hairline borders go soft. round(down, …, 1px) gives you fluid and crisp.

mod() and rem() differ only in whose sign survives, which matters exactly once — when one operand is negative:

mod(5px, 3px)   =  2px        rem(5px, 3px)   =  2px
mod(-5px, 3px)  =  1px        rem(-5px, 3px)  = -2px

Trigonometry — sin(), cos(), tan(), and friends

This is where the angle units stop being decoration and start doing arithmetic. The trig functions are the bridge between angles and plain numbers:

sin(<angle>) cos(<angle>) tan(<angle>)   → <number>
asin() acos() atan() atan2(y, x)         → <angle>

Since a <number> can be multiplied back into any type, “angle in, length out” becomes a one-liner. The canonical use is placing things on a circle without JavaScript:

.dot {
  --angle: 30deg;
  --radius: 8rem;
  translate:
    calc(cos(var(--angle)) * var(--radius))
    calc(sin(var(--angle)) * var(--radius));
}

Combined with sibling-index() (below) this distributes any number of children around a dial with no per-item CSS.The same identity underneath a clock face, a radial menu, and a pie chart — CSS can now express all three in the units it already had.


Exponentials — pow(), sqrt(), hypot(), log(), exp()

pow(), sqrt(), log(), and exp() are strictly numeric: numbers in, numbers out, no units allowed. hypot() is the exception, and the reason this family belongs in a guide about units: it accepts dimensioned values and returns one:

/* The diagonal of a 3×4 box, as a length */
--diagonal: hypot(3rem, 4rem);   /* = 5rem */

The numeric ones are how you build a modular type scale in CSS itself, instead of pasting values from a generator:

:root {
  --ratio: 1.25;
  --step-1: calc(1rem * pow(var(--ratio), 1));   /* 1.25rem */
  --step-2: calc(1rem * pow(var(--ratio), 2));   /* 1.5625rem */
  --step-3: calc(1rem * pow(var(--ratio), 3));   /* 1.953rem */
}

Change --ratio and the whole scale re-derives — the unit stays rem, only the multiplier moves.


abs() and sign()

abs(x)    magnitude, keeping the type:  abs(-2rem) = 2rem
sign(x)   direction, as a plain number: sign(-2rem) = -1

abs() is for offsets that must be positive whichever way a variable points; sign() turns a signed length into a -1 / 0 / 1 flag you can multiply by anything:

.tooltip {
  --offset: -12px;                          /* may be either sign */
  padding-block: abs(var(--offset));        /* always positive spacing */
  translate: calc(sign(var(--offset)) * 4px);
}

Chrome was five years behind Safari here, so this pair only became safe to use without a fallback in mid-2025.


Constants — pi, e, infinity, NaN

Inside any math function you can write these instead of typing digits:

--half-turn:  calc(1rad * pi);   /* π radians — the long way to say 0.5turn */
--e-squared:  pow(e, 2);         /* 7.389… */

(log() is already natural: log(x) is base e, and log(x, 10) when you want decades.)

infinity has one genuinely useful trick — the “pill” radius that can’t be beaten by content size:

.pill { border-radius: calc(infinity * 1px); }

It reads better than 9999px and says what it means: as round as this box can be.


Typed arithmetic

For most of CSS’s history, calc() let you multiply a length by a number but not divide one length by another. Typed arithmetic in CSS Values 4 changes that model: compatible typed values can now participate in unit algebra, so dividing one length by another can cancel the units and produce a plain <number>.

/* Divide a length by a length → unitless ratio */
--columns: calc(100cqi / 20rem);

/* Multiply a unitless value back into a type */
--angle: calc(var(--ratio) * 1turn);

The rules:

  • Division of same-type values cancels units → the result is a <number> you can reuse anywhere a number is allowed.
  • Multiplying a number by a typed value (* 1deg, * 1rem) converts it back into that type.
  • It works across the numeric types: <length>, <percentage>, <angle>, and so on.

The round-trip this unlocks — divide to a number, compute, then multiply back into a type — is the whole point:

/* turn the viewport width into a unitless number, then into an angle */
--vw-count: calc(100vw / 1px);              /* e.g. 1280 on a 1280px-wide viewport */
--spin:     calc(var(--vw-count) * 0.1deg); /* now usable wherever an <angle> is */

Support checked against MDN browser-compat data ·


The new frontier

Everything above is either cross-browser or, in the case of typed arithmetic, two engines in. This last group is younger still — worth knowing, worth using behind @supports, not worth depending on.

Function What it gives you Support
progress(v from a to b) a unitless 0–1 position of v between two typed values Chrome 138, Safari 26 — no Firefox
sibling-index() / sibling-count() the element’s position among its siblings, as an <integer> Chrome 138, Safari 26.2 — Firefox in preview
calc-size() lets intrinsic sizes (auto, max-content) take part in math Chrome 129 only
if() inline conditionals over style() / media() / supports() tests Chrome 137 only
random() a random value in a range, resolved once at computed-value time Safari 26.2 only

Support checked against MDN browser-compat data ·

Two of them matter for units specifically.

progress() does what typed division does, without the arithmetic: it maps a value onto a 0–1 scale, and both ends can carry units.

/* 0 at a 20rem viewport, 1 at 80rem — then use it anywhere a number fits */
--fluid: progress(100vw from 20rem to 80rem);
opacity: var(--fluid);
font-size: calc(1rem + var(--fluid) * 1.5rem);

sibling-index() finally gives CSS a counter usable inside calc(), so a stagger no longer needs one rule per child:

li {
  animation-delay: calc(sibling-index() * 60ms);
  rotate: calc(sibling-index() * (360deg / sibling-count()));
}

Multiply the integer by a unit and you have the delay, angle, or offset you wanted: the same “number × unit” move that typed arithmetic makes explicit.


Compact reference

Function What it does Takes units?
calc(expr) mixes units and does arithmetic; + and - need surrounding spaces yes
min(a, b, …) takes the smallest argument — acts as a ceiling yes
max(a, b, …) takes the largest argument — acts as a floor yes
clamp(min, pref, max) = max(min, min(pref, max)) yes
round(strategy?, v, step) snaps v to a multiple of step (nearest/up/down/to-zero) yes
mod(a, b) / rem(a, b) remainder; sign follows the divisor / the dividend yes
abs(x) / sign(x) magnitude, keeping the type / direction as -1, 0, 1 yes / returns number
hypot(a, b, …) √(a² + b² …) — the one exponential that keeps units yes
pow sqrt log exp exponents, roots, logarithms numbers only
sin cos tan <angle> (or radians) → <number> angle in
asin acos atan atan2 <number><angle> angle out
pi e infinity NaN constants inside any math function
Typed arithmetic Result
length ÷ length <number> — the units cancel
number × 1deg <angle> — converts a number back into a type

The fluid-type pattern to remember: clamp(2rem, 5vw + 1rem, 4rem) — the rem term keeps it zoom-accessible. And the recurring gotchas: fr is not allowed inside calc(), a pure-vw preferred value can fail WCAG 1.4.4, a bare number in sin() is radians, and a zero step in round() yields NaN.


Sources