How to generate a fluid font size with css clamp()
- Step 1Set the Min size — **Min px** is the smallest font size — the value used at and below the Min viewport. Range is `8`–`40`, step `1`, default `16`. Because the root size is fixed at 16, this is divided by 16 to produce the rem floor: 16px → `1.000rem`, 18px → `1.125rem`.
- Step 2Set the Max size — **Max px** is the largest font size — the value used at and above the Max viewport. Range is `12`–`120`, step `1`, default `24`. It becomes the rem ceiling (`maxPx / 16`). Keep Max greater than Min; the tool does not reject Max < Min (see the edge cases).
- Step 3Set the Min viewport — **Min vw** is the viewport width (in px) where the size equals your Min px. Range `200`–`1000`, step `10`, default `320` (a common small-phone width). Below this width the size stays pinned at the Min rem floor.
- Step 4Set the Max viewport — **Max vw** is the viewport width where the size equals your Max px. Range `800`–`2400`, step `40`, default `1440`. Max vw must be greater than Min vw or the tool throws `Max viewport must be greater than min viewport.` Above this width the size stays pinned at the Max rem ceiling.
- Step 5Generate the expression — Click **Generate**. The generator computes `slope = (maxPx - minPx) / (maxVw - minVw)`, the vw term `slope × 100`, and the rem intercept `(minPx - slope × minVw) / 16`, then assembles `clamp(minRem, interceptRem ± slopeVw, maxRem)`. Three result chips show **Min** (`16px @ 320px`), **Max** (`24px @ 1440px`) and the full **Expression**.
- Step 6Copy or download the CSS — **Copy to clipboard** grabs the whole block; **Download CSS** saves `clamp-fluid.css`. Paste the `font-size:` line directly on a selector, or use the `--font-size-fluid` custom property. For a whole scale of sizes (xs → 4xl) rather than one size, use the [typography-scale-builder](/font-tools/typography-scale-builder); to wire the value into a named token, use the [css-variable-generator-font](/font-tools/css-variable-generator-font).
The four inputs — real ranges from the schema
These are the exact bounds enforced by the number inputs in the tool's UI (lib/font/font-tool-schemas.ts). There are no other controls — no root-size field, no preset menu, no unit toggle.
| Field | Default | Min | Max | Step | What it controls |
|---|---|---|---|---|---|
Min px (fluidMin) | 16 | 8 | 40 | 1 | Font size at and below Min vw → becomes the rem floor (minPx/16) |
Max px (fluidMax) | 24 | 12 | 120 | 1 | Font size at and above Max vw → becomes the rem ceiling (maxPx/16) |
Min vw (fluidViewportMin) | 320 | 200 | 1000 | 10 | Viewport width where the size equals Min px |
Max vw (fluidViewportMax) | 1440 | 800 | 2400 | 40 | Viewport width where the size equals Max px |
Inputs to output — exact expressions
Computed with the tool's own buildClampExpression() (root font size hardcoded to 16, every number .toFixed(3)). These are the literal strings the generator emits.
| Min/Max px | Viewport range | Output clamp() |
|---|---|---|
| 16 → 24 | 320 → 1440 (defaults) | clamp(1.000rem, 0.857rem + 0.714vw, 1.500rem) |
| 16 → 24 | 320 → 1280 | clamp(1.000rem, 0.833rem + 0.833vw, 1.500rem) |
| 16 → 18 | 320 → 1440 | clamp(1.000rem, 0.964rem + 0.179vw, 1.125rem) |
| 24 → 36 | 320 → 1440 | clamp(1.500rem, 1.286rem + 1.071vw, 2.250rem) |
| 40 → 96 | 320 → 1440 | clamp(2.500rem, 1.500rem + 5.000vw, 6.000rem) |
The output block, both forms
Every run produces one CSS block with a comment header, a font-size line, and a custom property — filename clamp-fluid.css.
| Part of the output | Literal text emitted |
|---|---|
| Comment header | /* Fluid font size for 16px → 24px between 320px and 1440px viewports */ |
| Direct declaration | font-size: clamp(1.000rem, 0.857rem + 0.714vw, 1.500rem); |
| Custom property | :root { --font-size-fluid: clamp(1.000rem, 0.857rem + 0.714vw, 1.500rem); } |
Cookbook
Each recipe is a real set of inputs and the exact string the generator returns, so you can verify the tool against your own expectations. The root font size is fixed at 16 in all of them.
Body copy that scales gently
ExampleMost body text wants a small range — readable at 16px on a phone, a touch larger on desktop. A 16→18 range over 320→1440 gives a slope of just 0.179vw, so the size never feels like it is leaping around.
Inputs: Min 16px · Max 18px · Min vw 320 · Max vw 1440
Output:
font-size: clamp(1.000rem, 0.964rem + 0.179vw, 1.125rem);
Apply:
p { font-size: clamp(1.000rem, 0.964rem + 0.179vw, 1.125rem); }Section heading with visible interpolation
ExampleA mid-range heading (24px → 36px) over the default viewport range scales noticeably without being dramatic — a good fit for h2 / section titles.
Inputs: Min 24px · Max 36px · Min vw 320 · Max vw 1440
Output:
font-size: clamp(1.500rem, 1.286rem + 1.071vw, 2.250rem);
Apply:
h2 { font-size: clamp(1.500rem, 1.286rem + 1.071vw, 2.250rem); }Hero headline from phone to ultrawide
ExampleBrand-impact headlines push the range hard: 40px on a small phone, 96px on a wide desktop. The 5.000vw slope means the text grows fast — clamp() pins it at 6.000rem so it does not keep ballooning past 1440px.
Inputs: Min 40px · Max 96px · Min vw 320 · Max vw 1440
Output:
font-size: clamp(2.500rem, 1.500rem + 5.000vw, 6.000rem);
Apply:
h1 { font-size: clamp(2.500rem, 1.500rem + 5.000vw, 6.000rem); }Use the custom property the tool already emits
ExampleThe output block includes a :root custom property, so you do not need a separate variable tool for a single size. Reference --font-size-fluid wherever you need the same fluid value.
/* From the generator output, verbatim */
:root {
--font-size-fluid: clamp(1.000rem, 0.857rem + 0.714vw, 1.500rem);
}
.lead, .intro {
font-size: var(--font-size-fluid);
}Cap the growth earlier for dense dashboards
ExampleIf your app's max useful width is 1280, not 1440, set Max vw to 1280. The size hits its ceiling sooner and the slope is steeper to compensate over the shorter range.
Inputs: Min 16px · Max 24px · Min vw 320 · Max vw 1280 Output: font-size: clamp(1.000rem, 0.833rem + 0.833vw, 1.500rem); Note the slope rose from 0.714vw (1440 range) to 0.833vw (1280 range) because the same 8px climb now happens over fewer pixels of width.
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Max viewport not greater than Min viewport
Error — rejectedIf Max vw is less than or equal to Min vw, buildClampScale throws Max viewport must be greater than min viewport. and the result panel shows it in red. This is the only validation in the tool — every other combination produces output. The step on Min vw is 10 and on Max vw is 40, and their ranges overlap (200–1000 vs 800–2400), so it is possible to type a Max vw at or below the Min vw and trigger this.
Max size smaller than Min size
By design — negative slopeThe tool does not stop you setting Max px below Min px. With Min 24, Max 16 it emits clamp(1.500rem, 1.643rem - 0.714vw, 1.000rem) — a valid expression with a negative vw slope, meaning the text shrinks as the viewport grows. That is almost never what you want for typography, but it is mathematically correct for the numbers given. Check that Max px is larger than Min px before using the output.
Min equals Max
Expected — no scalingSet both sizes to 18 and you get clamp(1.125rem, 1.125rem + 0.000vw, 1.125rem). The slope is zero, so the font never changes size. It is a verbose way to write font-size: 1.125rem, but harmless — clamp() with identical bounds just returns that value at every viewport.
Root font size is fixed at 16
By designThere is no field to change the root size — buildClampScale always calls buildClampExpression(..., 16). So Min px is divided by 16 to get the rem floor regardless of what your page's actual root size is. If your html element runs at 18px or 20px, the rem values still assume 16, and the visual sizes will differ from the px you typed. The generator is computing CSS rem values, not absolute pixels for your specific root.
Output values are always 3 decimals
By designEvery number passes through .toFixed(3), so you always see exactly three decimals: 1.000rem, 0.714vw. This keeps the string short and avoids floating-point tails like 0.7142857142857143. The tiny rounding (sub-thousandth of a rem or vw) is invisible at any real screen size.
Below Min vw and above Max vw the size is pinned
Expected — clamp floor/ceilingclamp() interpolates only between the two viewport widths. At a 280px viewport (below a 320 Min vw) the preferred term would compute below the floor, so the browser uses the Min rem value. At a 1920px viewport (above a 1440 Max vw) it uses the Max rem value. This is the intended behaviour and why the bounds exist — the slope never runs away at extreme widths.
Very large Max px combined with a short viewport range
Supported — steep slopeMax px goes up to 120 and Min vw can be as low as 200. A 16→120px range over 200→800 yields a very steep vw slope. The expression is valid and clamp() still pins it at the ceiling past 800px, but on intermediate widths the text grows fast. Preview at a few in-between widths (480, 600) before shipping aggressive ranges.
It will not read a font file
By design — generative toolThis tool has needsFile: false and shows no upload zone. It cannot measure a font's metrics or scale relative to a specific typeface — it only does the clamp() arithmetic from the four numbers you enter. If you need sizes derived from a font's actual x-height or cap-height, analyse the file first with the font-metrics-analyzer, then bring the numbers here.
There is no live preview of the rendered text
By designThe result panel shows the CSS text and three metric chips, not a resizing preview of the font at different viewports. To eyeball the interpolation, paste the output into your own page or a browser devtools sandbox and drag the window. The tool's job is to compute the correct expression, not to render it.
Frequently asked questions
What does the generator actually output?
One CSS block: a comment line describing the range, a font-size: declaration with the clamp() expression, and a :root rule defining --font-size-fluid as the same expression. The file is named clamp-fluid.css on download. Three metric chips (Min, Max, Expression) summarise the run.
Why are the bounds in rem and the slope in vw?
rem bounds respect the user's browser font-size preference, which matters for accessibility — a px floor would override a user who has bumped their default size up. The vw middle term drives the smooth interpolation. This is the standard fluid-typography pattern, and it is what the tool emits: clamp(minRem, interceptRem ± slopeVw, maxRem).
Can I change the root font size used for the rem conversion?
No. The clamp generator hardcodes the root size to 16, so Min px is always divided by 16 to get the rem floor. There is no field for it. If your page's html element is not 16px, the rem numbers will not match the pixel sizes you typed at your actual root size.
How is the slope computed?
slope = (maxPx - minPx) / (maxVw - minVw), then the vw term is slope × 100. So a 16→24px climb over 320→1440 is 8 / 1120 = 0.00714…, times 100 is 0.714vw. The rem intercept is (minPx - slope × minVw) / 16. The tool does this exactly and rounds each piece to 3 decimals.
What happens if I set Max viewport below Min viewport?
The tool throws Max viewport must be greater than min viewport. and shows the error in red — no CSS is produced until you fix it. This is the only hard validation; it guards against a divide that would produce a nonsensical slope.
Does it stop me entering a Max size smaller than the Min size?
No. With Max px below Min px you get a valid clamp() with a negative vw slope (the text shrinks as the screen grows). It is mathematically correct but rarely intended — double-check that Max px is larger than Min px.
Is browser support good enough to use clamp() as the only strategy?
Yes in 2026. clamp() shipped in Chrome 79 (2019), Firefox 75 (2020) and Safari 13.1 (2020); Edge inherited it via Chromium. Coverage is well over 95% globally, so no polyfill is needed for the vast majority of audiences.
Do fluid font sizes cause layout shift (CLS)?
No, when applied consistently. clamp() resolves at layout time before CLS measurement begins, so the reserved space adjusts smoothly with the viewport rather than re-flowing at breakpoints. Stepped media-query sizing is actually the one more likely to introduce jumps at the breakpoint widths.
Should I use vw or vh for the slope?
vw. The generator's middle term is always vw-based, which is correct: reading typography should scale with the width people perceive, not the browser height. There is no option to switch to vh, and you would not want one for body or heading text.
How do I generate a whole scale of sizes, not just one?
Use the typography-scale-builder. It takes a base size and a modular ratio and emits a fluid clamp() expression for every step (xs through 4xl) at once. This clamp tool is deliberately single-size — one min, one max, one expression.
Is anything uploaded when I use this tool?
No. It is a pure generator with no file input — the result panel even shows a 0 bytes uploaded badge. Everything runs in your browser; the only server-side write is an anonymous usage counter for signed-in dashboard stats.
Can I run this from a script or the API?
The schema for the four options is exposed at GET /api/v1/tools/clamp-font-size-generator. The POST /api/v1/tools/clamp-font-size-generator/run endpoint is upload-free and routes execution through a paired @jadapps/runner on your own machine — but since the math is just a few lines, most automation pipelines reimplement buildClampExpression directly rather than calling the runner for a generative tool.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.