Fluid sizing with guardrails
Breakpoints jump. A headline that is 32px at 767px wide and 56px at 768px looks broken mid-resize, and covering every width with queries means writing a dozen of them.
Sometimes a size should glide instead, and clamp(min, preferred, max) keeps a value near its preferred size while never passing the guardrails.
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
}
.content {
width: clamp(320px, 90%, 60rem);
}| Argument | Role |
|---|---|
| min | the floor, never go below |
| preferred | used whenever it fits |
| max | the ceiling, never go above |
The h1 wants to be 5% of the viewport width, using vw from lesson 5-3, so it grows as the window does while never dropping below 2rem on phones or passing 3.5rem on large monitors. One line replaces three media queries.
The companion trick for images belongs in almost every stylesheet.
img {
max-width: 100%;
height: auto;
}Any image now shrinks to fit its container instead of overflowing on small screens, and height: auto keeps the aspect ratio intact while it does.
Tracing clamp at three viewport widths
clamp is simple enough to write directly, taking the preferred value and pushing it up to the min when too small or down to the max when too big.
function clamp(min, preferred, max) { return Math.min(Math.max(preferred, min), max); } // clamp(32, 5vw, 56) where 5vw = 0.05 * viewport width console.log(clamp(32, 0.05 * 500, 56)); console.log(clamp(32, 0.05 * 900, 56)); console.log(clamp(32, 0.05 * 1400, 56));
Output
32 45 56
| Viewport | 5vw | Result | Guardrail |
|---|---|---|---|
| 500px | 25 | 32 | min applied |
| 900px | 45 | 45 | preferred used |
| 1400px | 70 | 56 | max applied |
The nesting order matters, since Math.max raises to the floor first and Math.min then caps at the ceiling. Reversing them would still work here, and it breaks when a min is accidentally larger than a max, where CSS clamp also lets the min win.
Clamping a content column
contentColumnWidth(viewportWidth) implements the rule width: clamp(320px, 90%, 960px), where the 90% is measured against the viewport width.
function clamp(min, preferred, max) { return Math.min(Math.max(preferred, min), max); } function contentColumnWidth(viewportWidth) { return clamp(320, 0.9 * viewportWidth, 960); } console.log(contentColumnWidth(350)); console.log(contentColumnWidth(800)); console.log(contentColumnWidth(1400));
Output
320 720 960
| Viewport | 90% of it | Column width |
|---|---|---|
| 350px | 315 | 320 |
| 800px | 720 | 720 |
| 1400px | 1260 | 960 |
The preferred value is 0.9 * viewportWidth, and the rest is a single clamp call.
This is the standard reading-column rule. It leaves a small margin on phones, stays fluid through tablet widths, and stops widening on large monitors where over-long lines become hard to read.
When the floor takes over
On a 320px-wide viewport where 5vw is 16px, font-size: clamp(2rem, 5vw, 3.5rem) resolves to 2rem.
The preferred value of 1rem falls below the minimum guardrail, so clamp raises it to the min.
| Viewport | Preferred (5vw) | Result |
|---|---|---|
| 320px | 1rem | 2rem, the min |
| 800px | 2.5rem | 2.5rem |
| 1600px | 5rem | 3.5rem, the max |
On very large screens the max guardrail caps the value the same way. The middle row is the only one where the viewport actually drives the size, which is the band the fluid behavior lives in.
Naming the middle argument
In clamp(2rem, 5vw, 3.5rem), the middle value is the preferred value, used whenever it fits between the guardrails.
clamp(min, preferred, max) tries the preferred value first, raising it to the min when too small and capping it at the max when too big.
| Position | Name |
|---|---|
| first | min |
| second | preferred |
| third | max |
Making the preferred value viewport-relative, as 5vw does, is what produces the fluid glide. A fixed preferred value would produce a constant size with two guardrails that never come into play.