CSS clamp(), min(), and max()
clamp(), min(), and max() are CSS math functions that let a single value adapt to its context — the viewport, the parent’s size, or another property — without writing a single media query. Instead of picking one fixed number and hoping it works everywhere, you give the browser a formula and let it pick the right value on every resize, zoom, and reflow. They work anywhere a numeric CSS value is expected: widths, font sizes, padding, gaps, border-radius, and more. Mastering them is one of the biggest steps toward writing truly fluid, low-maintenance layouts.
Overview / How it works
These three functions come from the CSS Values and Units Module. Each one compares two or more comma-separated arguments and returns a single value of the same type (length, percentage, number, angle, time, and so on):
- min(a, b, …) returns the smallest of its arguments.
- max(a, b, …) returns the largest of its arguments.
- clamp(min, preferred, max) returns the preferred value, but never lets it drop below
minor rise abovemax.
Crucially, these are not evaluated once and frozen. The browser’s rendering engine re-evaluates them every time layout runs — on window resize, on zoom, on font-size changes affecting em/rem units, and (inside a container query context) on container resize. This makes them live participants in the box model, similar to how percentages and vw/vh units are recalculated on every layout pass, rather than fixed constants baked in at parse time.
Because they are math functions, you can mix unit types freely inside them — rem next to vw, % next to px — exactly as you can inside calc(). In fact you don’t need to wrap them in calc() at all; each argument is already evaluated as a calculation. You can even nest them inside calc(), inside each other, or reference CSS custom properties (variables) as arguments, which is what makes them so useful for design systems: a single --gutter variable can drive both a min() width and a max() padding across an entire stylesheet.
Browser support is excellent in all modern evergreen browsers (Chrome, Firefox, Safari, Edge), so you can use these functions freely on this course’s target platforms.
Syntax
| Function | Arguments | Returns |
|---|---|---|
clamp() |
Exactly 3, comma-separated: minimum, preferred, maximum | The preferred value, constrained between minimum and maximum |
min() |
2 or more, comma-separated | The smallest of all the arguments |
max() |
2 or more, comma-separated | The largest of all the arguments |
The general shape of a declaration using each function looks like this (illustrative only, not real values):
property: clamp(MIN, PREFERRED, MAX);
property: min(VALUE-1, VALUE-2, ...);
property: max(VALUE-1, VALUE-2, ...);
All arguments passed to a single call must resolve to the same value type — you cannot mix a length with an angle, for example, but you can freely mix length units like px, rem, %, and vw with each other since they all resolve to lengths.
Examples
Example 1: A fluid heading with clamp()
Minimal HTML this targets: an <h1 class="hero-title"> element containing a heading string.
.hero-title {
font-size: clamp(1.75rem, 1rem + 3vw, 3.5rem);
line-height: 1.15;
margin-block-end: 0.5em;
}
Result: On a narrow phone screen the heading renders at exactly 1.75rem (28px) because the preferred formula 1rem + 3vw evaluates below that floor. As the viewport widens, the text grows smoothly, pixel by pixel, with no jump at a breakpoint. Once the viewport is wide enough that 1rem + 3vw exceeds 3.5rem (56px), the size locks at 3.5rem and stops growing.
This is the classic “fluid typography” pattern: a floor, a scaling formula that blends a fixed base (1rem) with a viewport-relative amount (3vw), and a ceiling. The rem base keeps the value tied to the user’s root font-size preference, which matters for accessibility (see Best Practices below).
Example 2: min() to cap a width without overflow
Minimal HTML this targets: a <div class="card"> containing card content.
.card {
width: min(90%, 640px);
margin-inline: auto;
padding: 1.5rem;
border: 1px solid #d0d0d8;
border-radius: 0.5rem;
}
Result: On a small screen, the card takes up 90% of its container’s width, leaving a small margin on each side. On a large monitor, 90% of the container would be far too wide for comfortable reading, so once 90% would exceed 640px, the card simply stops growing and stays at 640px, centered by the auto inline margins. Only one declaration is needed instead of a base rule plus a media query override.
Example 3: max() with a custom property for a safe minimum gutter
:root {
--gutter: 1rem;
}
.container {
padding-inline: max(var(--gutter), 5vw);
width: min(100% - 2rem, 1200px);
margin-inline: auto;
}
Result: The side padding never drops below 1rem, even on the narrowest phones (where 5vw would be smaller than that), guaranteeing content never touches the screen edge. On wide screens, 5vw overtakes 1rem and the gutter scales up proportionally. Separately, the container’s width fills the available space minus a fixed 2rem gutter until it reaches a 1200px cap, after which it centers itself. This pattern — a variable feeding both a min() and a max() call — is exactly how design systems keep spacing consistent across dozens of components from one source of truth.
How it works step by step / Under the hood
When the rendering engine encounters clamp(MIN, PREFERRED, MAX) during layout, it performs the equivalent of these steps:
- 1. Resolve
MIN,PREFERRED, andMAXto concrete computed values in the current context (convertingvw,%,rem, and anyvar()references into comparable pixel-equivalent lengths). - 2. If
PREFERREDis less thanMIN, useMIN. - 3. Otherwise, if
PREFERREDis greater thanMAX, useMAX. - 4. Otherwise, use
PREFERREDas-is.
This means clamp() is mathematically identical to nesting min() and max() together — clamp(MIN, VAL, MAX) always produces the same result as max(MIN, min(VAL, MAX)):
.box {
width: clamp(200px, 50%, 600px);
}
.box-equivalent {
width: max(200px, min(50%, 600px));
}
Both rules produce identical layout: the browser first caps the 50% value at 600px with the inner min(), then ensures the result never drops below 200px with the outer max(). Seeing this equivalence helps demystify clamp() — it isn’t special magic, it’s shorthand for a very common min/max combination. Because the comparison happens during layout, not during CSS parsing, these values are re-computed on every resize event, every zoom level change, and every time an ancestor’s font-size (and therefore an em/rem-based argument) changes — the same live recalculation the engine already performs for plain percentages.
Common Mistakes
Mistake 1: Forgetting the commas between arguments
All three functions require comma-separated arguments. Space-separating them (a habit carried over from shorthand properties) produces an invalid, unparsable value:
.title {
font-size: clamp(1rem 4vw 3rem);
}
Because the three pieces aren’t separated by commas, the browser cannot tell where one argument ends and the next begins, so the entire declaration is treated as invalid and dropped — the element falls back to whatever font-size it would have inherited. The fix is simply to add the commas:
.title {
font-size: clamp(1rem, 4vw, 3rem);
}
Mistake 2: Putting the arguments in the wrong order
This version parses without error, which makes it a sneaky bug — the minimum and maximum have been swapped:
.badge {
font-size: clamp(3rem, 4vw, 1rem);
}
Per the specification, when the stated minimum is numerically larger than the stated maximum, the maximum value wins outright and the preferred value is ignored entirely. So this badge’s font-size is permanently frozen at 1rem on every screen size — the 4vw scaling never has any visible effect, and you get no error or warning, just a heading that mysteriously never grows. Always list the arguments in the order minimum, preferred, maximum:
.badge {
font-size: clamp(1rem, 4vw, 3rem);
}
Best Practices
- Always order
clamp()arguments as minimum, preferred, maximum — reversing them silently breaks the scaling with no error. - For fluid typography, blend a
remvalue into the preferred formula (e.g.1rem + 3vw) rather than using a bare viewport unit like4vwalone; a pure viewport-unit value ignores the user’s browser text-zoom preference, which is an accessibility problem. - Set the min and max bounds of
clamp()inrem, notpx, so the whole range scales if the user changes their root font size. - Reach for
min()/max()for simple constraints (cap a width, guarantee a minimum padding) and save media queries for structural changes, like switching from a single column to a multi-column grid. - Centralize shared bounds in custom properties (
--gutter,--content-max) so a single source of truth drivesmin()/max()calls across your whole stylesheet. - Test fluid values at browser zoom levels of 150–200% and at very small and very large viewport widths to confirm the floor and ceiling behave as intended.
- Avoid deeply nesting
clamp()insideclamp()— if you find yourself doing that, themax(MIN, min(VAL, MAX))equivalence is usually clearer to read.
Practice Exercises
- 1. Write a rule for
.panelso its width is fluid between 300px and 800px, filling 85% of its container in between, using a singleclamp()declaration. - 2. Given a custom property
--content-max: 65ch;, write a rule for.articleso its width never exceeds that value but still shrinks to fit narrower containers, usingmin(). - 3. Debug this declaration:
clamp(2rem, 1vw, 4rem)is applied to a page heading, but testers report it “barely changes size” between a phone and a desktop monitor. Explain why, using what you learned about how the preferred value is evaluated, and rewrite it so the heading visibly scales between roughly 320px and 1440px viewport widths.
Summary
min()returns the smallest of its arguments;max()returns the largest;clamp(min, preferred, max)returns the preferred value constrained between a floor and a ceiling.clamp(MIN, VAL, MAX)is equivalent tomax(MIN, min(VAL, MAX))— useful for understanding exactly what the browser computes.- All three functions are re-evaluated live during layout, responding to viewport resize, zoom, and font-size changes, just like percentages and viewport units.
- Arguments can mix unit types (e.g.
remandvw) and can include custom properties and nested math, without needing to wrap the whole thing incalc(). - Argument order matters for
clamp(): minimum, preferred, maximum — swapping min and max silently breaks the scaling instead of raising an error. - These functions let you replace many media-query breakpoints with a single fluid declaration, reducing CSS while improving how smoothly a design responds to any screen size.
