Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ~26 min· topic 7 of 7

Topic 3.7

CSS Maths Functions

In one line

calc() does arithmetic with mixed units, min() and max() pick the smaller or larger of several values, and clamp(MIN, PREFERRED, MAX) keeps a fluid value between limits; together they make sizes that adapt smoothly without media queries.

Think of it like this

A thermostat set to "follow the weather, but never colder than 18 degrees and never hotter than 24". The room temperature changes smoothly with the weather, yet always stays inside a safe range. clamp() is that thermostat for sizes: it follows the screen, but never below a minimum or above a maximum.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

calc()
A function that does arithmetic with CSS values, even mixed units, like calc(100% - 2rem).
min()
Uses the smallest of the values you give it. Acts as an upper limit.
max()
Uses the largest of the values you give it. Acts as a lower limit.
clamp()
clamp(MIN, PREFERRED, MAX): the preferred value, kept between a minimum and a maximum.
Fluid size
A size that grows smoothly with the screen, instead of jumping at set widths.
Fluid type
Font sizes that grow smoothly with the screen, usually written with clamp().
Slope
How much a value grows for each pixel the screen grows. Used to work out the vw part.
Breakpoint
A screen width where a media query switches to a different layout or size (CSS Phase 9).

Step by step

01calc mixes units the browser only knows later

You can't know what 100% is in pixels while writing CSS, but the browser can during layout. calc() hands it the formula.

calc.csswhole filecss
.sidebar-layout main {
  width: calc(100% - 250px);   /* room for a 250px sidebar */
}
.third { width: calc(100% / 3); }
.offset { margin-top: calc(var(--header-height) + 1rem); }

02min and max set limits, in reverse

Read min() as "no bigger than" the other values and max() as "no smaller than". A content column of width: min(100% - 2rem, 70ch) fills the screen on phones (with 1rem each side) and stops at a comfortable 70 characters on desktops, in one line.

column.csswhole filecss
.column {
  width: min(100% - 2rem, 70ch);  /* maths is allowed inside min() */
  margin-inline: auto;            /* centre it */
}
.sidebar { width: max(25%, 200px); }

03clamp keeps a value in range

clamp(MIN, PREFERRED, MAX) checks the preferred value against both limits. Below MIN it uses MIN; above MAX it uses MAX; otherwise the preferred value.

clamp keeps a value in rangediagram
Rendering diagram…

04Work out a fluid font size

Goal: 20px at a 400px wide screen and 32px at 1200px. Slope = (32 − 20) / (1200 − 400) = 0.015, which is 1.5vw. At 400px, 1.5vw is 6px, so the fixed part is 14px = 0.875rem. Keep the limits in rem too.

type.csswhole filecss
h1 {
  /* 20px at 400px wide, 32px at 1200px wide, steady outside that range */
  font-size: clamp(1.25rem, 0.875rem + 1.5vw, 2rem);
}

05Spacing can be fluid too

The same trick works for padding and gaps, so sections breathe more on big screens without media queries. Keep the ranges modest: tiny on phones, generous on desktops.

space.csswhole filecss
section {
  padding-block: clamp(2rem, 1rem + 4vw, 5rem);
  padding-inline: clamp(1rem, 5vw, 3rem);
}

Try it yourself

  1. 1

    Change the parent and predict

    In the first example, change .parent to width: 600px. Predict all four widths before running. (Only the max() bar keeps its old number. Why?)

  2. 2

    Break the spaces

    In the first example, change the calc to calc(100%-2 * 20px) (no spaces around the minus). Predict what happens. The declaration is invalid, the bar falls back to full width, and the console prints 400px.

  3. 3

    Design your own fluid size

    Work out a clamp() for a heading that is 24px at a 360px screen and 40px at a 1280px screen, then try it in the second example. (Slope = 16 / 920 ≈ 0.0174, so about 1.74vw.)

Code & diagrams

calc, min, max and clamp in a 400px box New tab

100% is 400px here. Work out each value before reading the console.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

calc(100% - 2 * 20px) = 360px
min(100%, 300px) = 300px
max(50%, 250px) = 250px
clamp(100px, 50%, 180px) = 180px
A fluid heading and a readable column New tab

The sizes depend on the preview's width, so they are shown on the page instead of the console.

Preview

Press Run to see the page.

Console

Nothing logged yet.
Variables plus calc: a spacing scale from one number New tab

One base number and a step per element. Change --unit to 4 and every padding halves.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

step 1 8px
step 2 16px
step 3 24px

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

No spaces around minus in calc

Write width: calc(100%-20px);.

terminal
$ DevTools → Styles
── what you'll see ──
The declaration is crossed out as "Invalid property value" and the element keeps its default width.

Break #2

Text that won't zoom

Set h1 { font-size: clamp(1rem, 5vw, 3rem); } and zoom the browser to 200% on a laptop.

terminal
$ Browser zoom 200%
── what you'll see ──
The heading stays about the same size on screen while the rest of the text doubles.

Myth vs fact

Myth

min() sets a minimum.

Fact

min() picks the smallest value, so it acts as a maximum limit: min(100%, 600px) is never more than 600px. max() acts as a minimum.

Myth

You need calc() inside min(), max() and clamp() to do maths.

Fact

These functions accept maths directly: min(100% - 2rem, 60ch) works without calc().

Myth

clamp() replaces all media queries.

Fact

It is great for smooth sizes, but layout changes (one column to three, hiding a menu) still need media queries or container queries (CSS Phases 9 and 10).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Maths functions are resolved as late as possible: the specified value keeps the formula, and percentages are only turned into pixels during layout. That is why getComputedStyle() for width returns pixels (the used value) but a custom property holding calc(...) reads back as text.

  • ▸

    Division by zero, or a result like calc(1px / 0), produces infinity, which is clamped to the largest value the browser supports. calc(infinity * 1px) is a legal (if odd) way to say "as large as possible". Negative results for properties that forbid them (like padding) are clamped to 0 at computed time rather than making the declaration invalid.

  • ▸

    In calc(), the types must work out: 100% - 20px is fine (length minus length), 10px * 2px is not (length × length). With typed arithmetic in recent specs, calc(10px / 2px) is a plain number, but browser support for dividing by a unit is still limited; prefer unitless variables and multiply by a unit.

  • ▸

    clamp() with vw responds to the viewport. For components that live in columns, the container query units (cqi, CSS Phase 10) in the preferred value make the size follow the component's slot instead of the window.

Remember this

  1. 1

    **calc() lets the browser do maths with values, even values in different units**: width: calc(100% - 2rem) means "the parent's width minus two root-font-sizes", which you can't know in advance. You can use +, -, * and /, and brackets. **+ and - must have a space on both sides** (100% - 2rem), because -2rem would otherwise read as a negative number. With *, at least one side must be a plain number; with /, the right side must be a plain number (calc(100% / 3)).

  2. 2

    **min(a, b, …) uses the smallest of its values, and max(a, b, …) the largest**. They read backwards at first: width: min(100%, 600px) means "600px, but never more than the parent": it works like a maximum width. max(50%, 300px) means "half the parent, but never less than 300px": a minimum. Each argument can itself be a calculation, without writing calc() inside.

  3. 3

    **clamp(MIN, PREFERRED, MAX)** returns the preferred value, held between the minimum and the maximum. It is exactly max(MIN, min(PREFERRED, MAX)). If MIN is larger than MAX, MIN wins. clamp(1rem, 4vw, 2rem) is 4vw when that falls between 16px and 32px, and stops at those limits on very small and very large screens.

  4. 4

    Fluid sizes grow smoothly with the screen instead of jumping at breakpoints. A fluid type setting like font-size: clamp(1.25rem, 0.9rem + 1.5vw, 2rem) grows the heading from 20px on phones to 32px on wide screens. The preferred value mixes rem and vw on purpose: the rem part keeps responding to browser zoom and the reader's font-size setting (Topic 3.3 explained why vw alone fails the WCAG 200% zoom rule), and the vw part adds the growth.

  5. 5

    To design a fluid value from two points ("20px at a 400px screen, 32px at a 1200px screen"): the slope is (32 − 20) / (1200 − 400) = 0.015, so the vw part is 1.5vw (100 × slope). At 400px that gives 6px, so the fixed part is 20 − 6 = 14px = 0.875rem. Result: clamp(1.25rem, 0.875rem + 1.5vw, 2rem). Many online "fluid type calculators" do this sum for you.

  6. 6

    These functions work almost anywhere a number, length, percentage, angle or time is allowed, and can nest (calc(min(100%, 60ch) - 2rem)) and take custom properties (calc(var(--space) * 2), Topic 3.6). The browser keeps them as formulas and recalculates them during layout, so a percentage or viewport unit inside stays live when the window resizes. Newer functions follow the same idea: round(), mod(), rem(), abs(), sign(), and trigonometry such as sin() and cos().

Explain it without notes

01

Why does calc() need spaces around + and - but not around * and /?

02

Explain min() and max() and why they feel backwards.

03

Write the formula clamp() is equivalent to, and explain each argument.

04

How do you build an accessible fluid font size between two screen widths?

Practice

01

Make an image wrapper .media that is 50% of its parent, but never narrower than 200px and never wider than 480px.

02

Lay out three .col boxes side by side with display: inline-block, each exactly one third of the parent minus a 1rem gap share, using calc().

03

Write a fluid h2 that is 18px at a 360px screen and 28px at a 1360px screen.

Trade-offs

  • ↔

    Fluid clamp() sizes remove many breakpoints and look smooth, but the exact size at a given width is harder to read from the code and to match to a pixel-perfect design.

  • ↔

    calc() with variables makes systems from a few numbers, but long nested formulas are hard to debug; DevTools' Computed pane shows the final result.

Done when you can

  • I can write a valid calc() with mixed units and correct spacing.

  • I can use min() as a maximum and max() as a minimum.

  • I can explain clamp() and its equivalent max(MIN, min(VAL, MAX)).

  • I can calculate a fluid size between two screen widths.

  • I can keep fluid text accessible by mixing rem with vw.