Most of the time you hand CSS a fixed value — width: 300px, font-size: 18px — and that’s that. But real layouts are rarely so tidy. You’ll hit moments where the value you want isn’t a single number you can type out, but a calculation: “the full width, minus this sidebar,” or “20% of the viewport, but never smaller than 16 pixels.” For years that meant reaching for JavaScript or giving up and eyeballing it.
CSS has a better answer: math functions. With calc(), min(), max(), and clamp(), the browser does the arithmetic for you, live, as the screen resizes. They’re the secret behind layouts that flex gracefully without a single line of JavaScript, and once you get them they quietly become part of your everyday toolkit. Let’s walk through all four.
calc(): doing arithmetic right in CSS
The calc() function lets you write a math expression as a value. The browser evaluates it and uses the result:
.box {
width: calc(100% - 40px);
}
That reads as “100% of the available width, minus 40 pixels.” This is the headline feature of calc(): it can mix units freely. Percent and pixels, rem and vw, em and px — things you could never combine by hand, because the browser only knows what 100% resolves to at render time. You can’t precompute 100% - 40px yourself; calc() can, on the fly.
You get the four basic operators:
| Operator | Meaning |
|---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
.sidebar {
width: calc(100% / 3); /* exactly one third */
}
.banner {
height: calc(100vh - 80px); /* full screen height minus a fixed header */
}
.spacing {
margin: calc(1rem + 5px); /* a rem plus a few pixels */
}
That 100vh - 80px pattern is a classic: a hero section that fills the whole screen except for a fixed 80px navbar sitting above it. No JavaScript measuring required — the browser keeps it correct as the window resizes.
The spaces around + and - are mandatory
Inside calc(), you must put a space on both sides of the + and - operators. calc(100% - 40px) works; calc(100%-40px) is silently ignored and your value just doesn’t apply. The reason is that -40px could be read as a single negative number, so the spaces tell CSS you mean subtraction. For * and / the spaces are optional, but it’s a good habit to space everything consistently.
You can nest expressions with parentheses, and you can even nest calc() inside calc() (though plain parentheses usually do the job):
.card {
width: calc((100% - 2 * 16px) / 3); /* 3 columns with two 16px gaps */
}
Here you’re carving a row into three equal columns while accounting for the gaps between them — the kind of thing that’s genuinely painful to hardcode but trivial with a quick expression.
min(): pick the smallest value
min() takes a list of values and uses whichever is smallest at that moment. It sounds backwards at first, but it’s incredibly handy for setting a cap:
.container {
width: min(90%, 1200px);
}
Read that as: “use 90% of the available width, unless that would exceed 1200px — then cap it at 1200px.” On a small phone, 90% is the smaller number, so the container is fluid and breathes with the screen. On a huge monitor, 1200px becomes the smaller number, so the content stops sprawling and stays a comfortable, readable width. One line replaces a width plus a max-width plus a media query.
.title {
font-size: min(8vw, 64px); /* scales with the screen, but never past 64px */
}
The mental shortcut: min() sets a maximum. Because it always grabs the smaller value, the largest item in the list acts as a ceiling that the result can never climb above.
max(): pick the largest value
max() is the mirror image — it uses whichever value is largest, which makes it perfect for setting a floor:
.text {
font-size: max(16px, 2vw);
}
This says: “scale the font with the viewport at 2vw, but never let it drop below 16px.” On a tiny screen, 2vw might compute to something like 8px — too small to read comfortably — so max() falls back to the larger 16px and keeps your text legible. On a wide screen, 2vw wins and the text grows.
.gutter {
padding: max(1rem, 3vw); /* comfortable padding that never gets cramped */
}
The mental shortcut here is the flip of the last one: max() sets a minimum. It always grabs the larger value, so the smallest item in the list becomes a floor the result can never sink below.
min sets a max, max sets a min
The naming trips up almost everyone at first. Remember it by what the function guarantees: min() guarantees the result won’t go above its biggest argument (a maximum), and max() guarantees it won’t go below its smallest argument (a minimum). Say it out loud a couple of times — “min caps, max floors” — and it sticks.
clamp(): a floor, a target, and a ceiling
Now for the star of the show. clamp() combines both ideas into one elegant function. It takes three values — a minimum, a preferred value, and a maximum:
.heading {
font-size: clamp(1.5rem, 4vw, 3rem);
}
The order is always minimum, preferred, maximum, and here’s how to read it:
- The browser wants to use the middle value,
4vw— a size that scales with the screen. - But it will never let the result fall below
1.5rem(the floor). - And it will never let it climb above
3rem(the ceiling).
So on a small phone, the heading sits at its 1.5rem floor. As the screen grows, the 4vw preferred value takes over and the text scales up smoothly. On a giant monitor, it stops at 3rem and refuses to get any bigger. You get fluid typography that flows naturally between two sensible limits — and it’s all in a single line.
This is the modern way to do responsive font sizes. Before clamp(), you’d write a base size and then layer on media queries to bump it up at each breakpoint, creating jumpy, stair-stepped jumps in size. clamp() makes the change continuous and smooth instead.
/* Fluid spacing and widths work too — not just fonts */
.section {
padding-block: clamp(2rem, 6vw, 5rem);
}
.wrapper {
width: clamp(320px, 90%, 1200px);
}
clamp(a, b, c) is just shorthand for max(a, min(b, c))
If you ever want to prove to yourself how clamp() works, it’s literally equivalent to max(MIN, min(PREFERRED, MAX)). The inner min() caps the preferred value at the maximum, and the outer max() lifts it up to the minimum if it fell too low. clamp() just wraps that pattern in one readable function — which is exactly why it’s so popular.
Combining functions
These functions compose. You can drop a calc() expression inside a min(), max(), or clamp(), which unlocks more expressive rules:
.hero-title {
/* floor 2rem, scale fluidly, ceiling that leaves room on huge screens */
font-size: clamp(2rem, 1.5rem + 3vw, 4rem);
}
That preferred value, 1.5rem + 3vw, is a small calculation in its own right — a fixed base plus a screen-relative part. (Notice you don’t even need to write calc() here; inside clamp(), min(), and max(), math expressions are allowed directly.) This particular shape — a rem base plus a vw slope — is the go-to recipe for fluid type that feels balanced, because the fixed part keeps small screens readable while the vw part adds growth.
A practical layout
Let’s put it together into a content wrapper you’d genuinely use on a real page — fluid, centered, and capped:
.content {
width: min(92%, 1100px); /* fluid, but caps at 1100px */
margin-inline: auto; /* centers it */
padding: clamp(1rem, 4vw, 2.5rem);
}
.content h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
}
<main class="content">
<h1>Welcome to ACY Partner Indonesia</h1>
<p>This content stays readable on a phone and elegant on a desktop.</p>
</main>
Look at what you got for almost no code: the wrapper fills the screen on a phone but never sprawls wider than 1100px on a desktop, the padding grows with the viewport but stays within sane bounds, and the heading scales fluidly between a comfortable floor and ceiling. There’s not a single media query in sight, and not one line of JavaScript. That’s the quiet power of CSS math functions.
If you’re still nailing down which units to feed these functions — when to reach for vw, rem, %, or px — it’s worth revisiting CSS units first, since these functions are only as good as the units you mix into them. And once your sizes flex on their own, you’ll lean far less on the breakpoint-heavy approach from responsive design — many things that once needed a media query now just… resize themselves.
Browser support and gotchas
All four functions enjoy excellent support across every modern browser, so you can use them freely today. A few things worth keeping in mind:
- Spacing in
calc()— as covered above,+and-need spaces around them, every time. - Division by zero —
calc(100% / 0)is invalid and the declaration is dropped. Watch out when a variable could be zero. - Unit mismatches — you can’t add a unitless number to a length:
calc(10px + 5)is invalid. Every term that needs a unit must have one. - Readability — these can get dense. A short comment explaining why a
clamp()has the limits it does will thank you later.
Don't bury fixed magic numbers inside calc()
It’s tempting to write things like calc(100vh - 73px) where 73px is your header’s exact height. The problem: the day someone changes the header to 80px, your calc() silently breaks and nothing tells you why. Where you can, store such values in a CSS custom property (a variable) and reference it — calc(100vh - var(--header-height)) — so there’s a single source of truth. It keeps these expressions maintainable as the design evolves.
Wrapping up
CSS math functions let the browser handle arithmetic for you, in real time, as the layout changes. They’re the cleanest path to fluid, responsive design without JavaScript:
calc()does arithmetic and, crucially, lets you mix units like%andpx. Always space your+and-.min()picks the smallest value — use it to set a maximum (a ceiling your value can’t exceed).max()picks the largest value — use it to set a minimum (a floor your value can’t drop below).clamp(min, preferred, max)combines both into fluid sizing that scales between a floor and a ceiling — perfect for responsive typography and spacing.- They compose: nest math expressions inside
clamp(),min(), andmax()for expressive, self-adjusting values.
Master these four and a whole class of layout problems simply dissolves. You stop writing pixel values and start describing relationships — “this big, but never bigger than that” — and let the browser sort out the rest. Next time you reach for a media query to resize something, pause and ask whether a clamp() could do it in one line instead.