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
vwpart. - 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.
.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 {
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.
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.
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.
section {
padding-block: clamp(2rem, 1rem + 4vw, 5rem);
padding-inline: clamp(1rem, 5vw, 3rem);
}Try it yourself
- 1
Change the parent and predict
In the first example, change
.parenttowidth: 600px. Predict all four widths before running. (Only themax()bar keeps its old number. Why?) - 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 prints400px. - 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 about1.74vw.)
Code & diagrams
100% is 400px here. Work out each value before reading the console.
Preview
Console
Nothing logged yet.Expected console output
calc(100% - 2 * 20px) = 360px
min(100%, 300px) = 300px
max(50%, 250px) = 250px
clamp(100px, 50%, 180px) = 180pxThe sizes depend on the preview's width, so they are shown on the page instead of the console.
Preview
Console
Nothing logged yet.One base number and a step per element. Change --unit to 4 and every padding halves.
Preview
Console
Nothing logged yet.Expected console output
step 1 8px
step 2 16px
step 3 24pxBreak 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);.
Break #2
Text that won't zoom
Set h1 { font-size: clamp(1rem, 5vw, 3rem); } and zoom the browser to 200% on a laptop.
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()forwidthreturns pixels (the used value) but a custom property holdingcalc(...)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 (likepadding) are clamped to 0 at computed time rather than making the declaration invalid. - ▸
In
calc(), the types must work out:100% - 20pxis fine (length minus length),10px * 2pxis 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()withvwresponds 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
**
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-2remwould 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
**
min(a, b, …)uses the smallest of its values, andmax(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 writingcalc()inside. - 3
**
clamp(MIN, PREFERRED, MAX)** returns the preferred value, held between the minimum and the maximum. It is exactlymax(MIN, min(PREFERRED, MAX)). If MIN is larger than MAX, MIN wins.clamp(1rem, 4vw, 2rem)is4vwwhen that falls between 16px and 32px, and stops at those limits on very small and very large screens. - 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 mixesremandvwon purpose: therempart keeps responding to browser zoom and the reader's font-size setting (Topic 3.3 explained whyvwalone fails the WCAG 200% zoom rule), and thevwpart adds the growth. - 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
vwpart is1.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
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 assin()andcos().
Explain it without notes
Why does calc() need spaces around + and - but not around * and /?
Explain min() and max() and why they feel backwards.
Write the formula clamp() is equivalent to, and explain each argument.
How do you build an accessible fluid font size between two screen widths?
Practice
Make an image wrapper .media that is 50% of its parent, but never narrower than 200px and never wider than 480px.
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().
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 andmax()as a minimum.I can explain
clamp()and its equivalentmax(MIN, min(VAL, MAX)).I can calculate a fluid size between two screen widths.
I can keep fluid text accessible by mixing
remwithvw.