How to build a modular typography scale with fluid clamp()
- Step 1Set the base size — The **Base size (px)** field accepts 10–32 (default 16). 16px is the browser default and keeps `--font-size-base` at exactly `1.000rem`. Note that `rem` in the output is computed as `px / baseSize`, so if you set base to 18 the `clamp()` slope is also calculated against 18 as the root — fine if your `html` font-size is 18px, surprising if it is 16px.
- Step 2Pick a ratio from the dropdown — The **Ratio** select offers eight presets: Minor Second (1.067), Major Second (1.125), Minor Third (1.2), Major Third (1.25), Perfect Fourth (1.333), Augmented Fourth (1.414), Perfect Fifth (1.5), Golden Ratio (1.618). There is no free-text ratio box on the page — choose the nearest preset, or use the runner API if you need a bespoke number (see the build-pipeline guide).
- Step 3Choose the number of steps — The **Steps** slider runs 3–12 (default 8). Steps map to the fixed label list in order. With the default origin index of 2, step 0 is `xs`, step 2 is `base`, and everything above climbs the ratio. More steps means a larger top size — a Golden-Ratio 12-step scale reaches into the hundreds of px.
- Step 4Press the action and read the result panel — The result panel shows the generated CSS plus metrics: Steps, Ratio (to 3 dp), Smallest px, and Largest px. There is no live web-page preview of the type itself — the panel renders the CSS text and the four headline numbers, which is enough to sanity-check the range before you copy.
- Step 5Copy the static or the fluid block (or both) — The file contains the static rem scale first, then the fluid `clamp()` scale inside `@media (min-width: 320px)`. For most production sites the fluid block alone is enough; keep the static block if you want exact, viewport-independent sizes or a non-fluid fallback. Both use the same token names, so they are interchangeable.
- Step 6Rename tokens in your editor if needed — The token names are baked in (`xs`…`8xl`) — there is no rename control in the tool. If your design system uses `--text-sm` or `--fs-200`, find-and-replace the prefix in your editor after pasting. The numeric values are what matter; the names are just a starting convention.
What the three on-page controls actually do
These are the only controls rendered on the page. Origin index and the fluid viewport bounds exist in the tool schema but are not exposed in the UI — they fall back to the defaults shown in the next table.
| Control | Type / range | Default | Effect on output |
|---|---|---|---|
| Base size (px) | Number, 10–32, step 1 | 16 | Sets the value at the origin step and the rem divisor. rem = px / baseSize, so base 16 → --font-size-base: 1.000rem. |
| Ratio | Select, 8 named presets | Major Third (1.25) | The multiplier between adjacent steps. No arbitrary numeric input on the page — pick the nearest preset. |
| Steps | Range slider, 3–12, step 1 | 8 | How many size tokens to emit. Mapped in order to the fixed label list xs, sm, base, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, 8xl. |
Defaults used for the parameters the UI does not expose
The builder accepts these via its schema (and the runner API), but the on-page form has no field for them, so a browser run always uses these values.
| Parameter | Default in UI runs | What it controls |
|---|---|---|
scaleOrigin | 2 | Where the base size sits in the step list. At 2, step 0 is xs and the base is the third token. Higher origin pushes more small sizes below the base. |
fluidViewportMin | 320 px | Lower bound of the @media query and the clamp() interpolation start. |
fluidViewportMax | 1280 px | Upper bound the clamp() slope interpolates to. |
| Per-step fluid min/max | 0.85× / 1.15× the static px | The builder does not re-sample the scale at each viewport — it shrinks the static px by 15% for the min and grows it 15% for the max, then builds the clamp() between those. |
Major Third (1.25) at 16px base, 8 steps — the default output
Exact values the builder emits with all defaults. The px column is shown as a trailing comment in the static block; the rem is px / 16.
| Token | px | Static rem | Fluid clamp() |
|---|---|---|---|
--font-size-xs | 10.24 | 0.640rem | clamp(0.544rem, 0.480rem + 0.321vw, 0.736rem) |
--font-size-sm | 12.8 | 0.800rem | clamp(0.680rem, 0.600rem + 0.400vw, 0.920rem) |
--font-size-base | 16 | 1.000rem | clamp(0.850rem, 0.750rem + 0.500vw, 1.150rem) |
--font-size-lg | 20 | 1.250rem | clamp(1.063rem, 0.938rem + 0.625vw, 1.438rem) |
--font-size-xl | 25 | 1.563rem | clamp(1.328rem, 1.172rem + 0.781vw, 1.797rem) |
--font-size-2xl | 31.25 | 1.953rem | clamp(1.660rem, 1.465rem + 0.977vw, 2.246rem) |
--font-size-3xl | 39.06 | 2.441rem | clamp(2.075rem, 1.831rem + 1.221vw, 2.808rem) |
--font-size-4xl | 48.83 | 3.052rem | clamp(2.594rem, 2.289rem + 1.525vw, 3.509rem) |
Cookbook
Real, copy-pasteable output from the builder at different settings. Every block below is byte-for-byte what the tool emits — the static scale, the fluid scale, or both. For a single one-off size instead of a whole scale, use the clamp() font-size generator; to wrap these tokens in a richer variable set see the font CSS variable generator.
The default file (Major Third, 16px, 8 steps)
ExamplePress the action with no changes and you get exactly this — a static block, then a fluid block gated behind a 320px media query. Both share the same token names so a component can reference either.
/* Modular type scale
* base: 16px · ratio: 1.25 · steps: 8
* Two variants below: a static rem scale and a fluid clamp() scale.
*/
:root {
/* Static scale */
--font-size-xs: 0.640rem; /* 10.24px */
--font-size-sm: 0.800rem; /* 12.8px */
--font-size-base: 1.000rem; /* 16px */
--font-size-lg: 1.250rem; /* 20px */
--font-size-xl: 1.563rem; /* 25px */
--font-size-2xl: 1.953rem; /* 31.25px */
--font-size-3xl: 2.441rem; /* 39.06px */
--font-size-4xl: 3.052rem; /* 48.83px */
}
@media (min-width: 320px) {
:root {
/* Fluid scale (auto-interpolates between 320px and 1280px viewports) */
--font-size-xs: clamp(0.544rem, 0.480rem + 0.321vw, 0.736rem);
--font-size-sm: clamp(0.680rem, 0.600rem + 0.400vw, 0.920rem);
--font-size-base: clamp(0.850rem, 0.750rem + 0.500vw, 1.150rem);
--font-size-lg: clamp(1.063rem, 0.938rem + 0.625vw, 1.438rem);
--font-size-xl: clamp(1.328rem, 1.172rem + 0.781vw, 1.797rem);
--font-size-2xl: clamp(1.660rem, 1.465rem + 0.977vw, 2.246rem);
--font-size-3xl: clamp(2.075rem, 1.831rem + 1.221vw, 2.808rem);
--font-size-4xl: clamp(2.594rem, 2.289rem + 1.525vw, 3.509rem);
}
}Wire the tokens into headings
ExampleThe output is variables only — it does not assign them to elements. Map the tokens to your h1–h6 yourself. With the default origin, base sits at index 2, so body uses base and headings climb.
/* After pasting the :root blocks above */
body { font-size: var(--font-size-base); }
h6 { font-size: var(--font-size-lg); }
h5 { font-size: var(--font-size-xl); }
h4 { font-size: var(--font-size-2xl); }
h3 { font-size: var(--font-size-3xl); }
h2 { font-size: var(--font-size-4xl); }
h1 { font-size: var(--font-size-4xl); } /* bump steps to 9 for a distinct h1 */
small { font-size: var(--font-size-sm); }A subtle, reading-first scale (Minor Third, 6 steps)
ExampleDrop the ratio to Minor Third (1.2) and steps to 6 for a quiet hierarchy where body text dominates — common on docs and long-form blogs. Same default origin (2) and 320–1280 fluid range.
/* base: 16px · ratio: 1.2 · steps: 6 */
:root {
/* Static scale */
--font-size-xs: 0.694rem; /* 11.11px */
--font-size-sm: 0.833rem; /* 13.33px */
--font-size-base: 1.000rem; /* 16px */
--font-size-lg: 1.200rem; /* 19.2px */
--font-size-xl: 1.440rem; /* 23.04px */
--font-size-2xl: 1.728rem; /* 27.65px */
}A dramatic display scale (Golden Ratio, 18px base)
ExampleGolden Ratio (1.618) with an 18px base produces a big jump between steps — editorial and luxury work. Note how fast the top grows: by 4xl you are already over 76px. Watch the largest-size metric in the result panel.
/* base: 18px · ratio: 1.618 · steps: 6 */
:root {
/* Static scale */
--font-size-xs: 0.382rem; /* 6.88px */
--font-size-sm: 0.618rem; /* 11.12px */
--font-size-base: 1.000rem; /* 18px */
--font-size-lg: 1.618rem; /* 29.12px */
--font-size-xl: 2.618rem; /* 47.12px */
--font-size-2xl: 4.236rem; /* 76.24px */
}Use only the static block (no fluid)
ExampleIf your design needs exact, viewport-independent sizes — print stylesheets, fixed-width apps, embedded dashboards — delete the @media block and keep just the first :root. The token names are identical, so nothing downstream changes.
/* Keep this; drop the @media (min-width: 320px) block */
:root {
--font-size-xs: 0.640rem;
--font-size-sm: 0.800rem;
--font-size-base: 1.000rem;
--font-size-lg: 1.250rem;
--font-size-xl: 1.563rem;
--font-size-2xl: 1.953rem;
--font-size-3xl: 2.441rem;
--font-size-4xl: 3.052rem;
}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.
You want a ratio that is not one of the eight presets
By designThe on-page Ratio control is a dropdown of eight named ratios — there is no free-text numeric field. So 1.15 or 1.4 are not selectable from the page. Pick the nearest preset (Major Second 1.125 or Augmented Fourth 1.414), or hit the runner API with a numeric scaleRatio value, which the underlying handler accepts directly. The build-pipeline guide shows the API call.
Origin and fluid viewport fields are missing from the page
By designThe schema defines scaleOrigin, fluidViewportMin, and fluidViewportMax, but the rendered form only shows Base size, Ratio, and Steps. Every browser run therefore uses origin 2 and a 320→1280px fluid range. To change them you have to call the tool through the runner API where the full option set is honoured.
Base size set to 18 but your html element is still 16px
Watch outrem is computed as px / baseSize, and the clamp() slope uses the base as the root font size. With base 18, --font-size-base is 1.000rem and the math assumes 1rem = 18px. If your html { font-size } is the browser default 16px, every value renders ~11% smaller than the px comment suggests. Either set html { font-size: 18px } or keep base at 16 and scale visually with the ratio.
Golden Ratio at 12 steps produces enormous top sizes
ExpectedThere is no clamp on the largest output — base × ratio^(steps-1-origin) can run into the hundreds of px. Golden Ratio at 18px base, 9 steps already hits ~323px at the top token. The result panel's Largest metric shows this before you copy. Reduce steps or pick a gentler ratio if the top size is unusable.
Steps above 8 use 5xl–8xl labels
SupportedThe label list is xs, sm, base, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, 8xl — twelve names for the 3–12 step range, so every step gets a name. There is no 2xs token; with origin 2 the smallest is always xs. If you need a token below xs, lower the base or shift origin via the API.
The fluid min/max are not a re-sampled scale
By designEach fluid step is clamp() built from the static px shrunk to 0.85× (min) and grown to 1.15× (max) — a flat ±15% band, not a second modular computation at the two viewport widths. This keeps neighbouring fluid steps from overlapping but means the fluid min is not itself on your chosen ratio. If you need precise per-viewport control, build individual clamp() values with the clamp() generator.
rem comment math looks off because px is rounded
ExpectedThe px values are rounded to two decimals (Math.round(x*100)/100), then the rem is roundedPx / baseSize to three decimals. So 25px / 16 shows 1.563rem (not 1.5625). The tiny rounding is intentional and well under a sub-pixel — it keeps the file readable. If you need exact ratios for a token pipeline, recompute from base and ratio yourself.
No live preview of the actual typography
By designThe result panel shows the CSS text and four metrics (Steps, Ratio, Smallest, Largest) — it does not render a paragraph in each size. To eyeball the scale, paste the output into a CodePen or your project and apply the tokens. This tool generates CSS; it is not a type tester.
Output is CSS only — no JSON, Swift, or Android tokens
By designThe tool emits a single text/css file (type-scale.css). It does not produce W3C Design Tokens JSON, a Swift type ramp, or an Android dimens.xml. To feed a cross-platform token pipeline, parse the rem/px values out of the CSS (or call the runner and read the same values) and transform them yourself — see the build-pipeline guide.
Frequently asked questions
What exactly does the builder output?
One CSS file named type-scale.css with two :root blocks. The first is a static rem scale (--font-size-xs … through your step count) with the px value in a trailing comment. The second is a fluid clamp() scale inside @media (min-width: 320px), using the same token names. You can keep either or both.
Which ratio should I pick?
Minor Third (1.2) is subtle and good for body-heavy reading sites. Major Third (1.25) is the popular default and the on-page default. Perfect Fourth (1.333) is expressive and editorial. Golden Ratio (1.618) is dramatic and headline-driven but grows fast — watch the Largest metric. There are eight presets in the dropdown; the named ratios reference has the full table.
Can I enter a custom ratio like 1.15?
Not from the page — the Ratio control is a fixed dropdown of eight named values. Pick the closest preset, or call the tool through the JAD runner's local API where the handler accepts a numeric scaleRatio directly. The build-pipeline guide shows the request shape.
Why are there only three controls when the docs mention origin and viewport?
The tool's option schema defines scaleOrigin, fluidViewportMin, and fluidViewportMax, but the on-page form renders only Base size, Ratio, and Steps. Browser runs therefore always use origin 2 and a 320→1280px fluid range. The extra parameters are reachable through the runner API, not the web UI.
How is the fluid clamp() built?
For each step the builder takes the static px, makes a min of 0.85× and a max of 1.15×, then constructs clamp(minRem, interceptRem ± slopeVw, maxRem) that interpolates linearly between 320px and 1280px viewports. The rem and vw are derived from the base size. It is a ±15% band around the static size, not a second scale computed at each viewport.
What's the difference between the static and fluid blocks?
Static is plain rem — the size never changes with the viewport, predictable and easy to debug. Fluid is clamp() — the size grows smoothly as the window widens, between the 320px and 1280px bounds. Fluid is the modern default for responsive type; static is useful for fixed-width layouts, print, or as a no-clamp fallback.
What base size should I use?
16px in almost all cases — it is the browser default and keeps --font-size-base at exactly 1.000rem. Use 18px for content-heavy reading layouts or 14px for dense UI, but remember the output's rem is px / baseSize, so if you change the base you should set your html { font-size } to match or the rendered sizes will drift from the px comments.
How many steps should I generate?
8–10 covers most design systems: with the default origin you get xs, sm, base, lg, xl, 2xl, 3xl, 4xl at 8 steps. Fewer (5–6) suits minimal UIs; more (10–12) suits sites with big display headings. The slider goes 3–12. Going high with a dramatic ratio produces very large top sizes — check the Largest metric.
Can I rename the tokens?
Not in the tool — the names xs through 8xl are baked into the output. After pasting, find-and-replace the prefix in your editor if your system uses --text-* or --fs-*. The numeric values are the real product; the names are a convention you can swap freely.
Does it show me a preview of the typography?
No. The result panel renders the CSS text plus four metrics: Steps, Ratio (3 dp), Smallest px, and Largest px. There is no rendered specimen. To see the scale applied to real text, paste the tokens into your project or a sandbox. For inspecting an actual font's glyphs and metrics, use the font metrics analyzer.
Do I have to upload a font?
No — this is a generative tool. It produces CSS from numbers alone; there is no font file involved and no tier file-size limit. It runs entirely in your browser on the free tier with no account.
How do I get a single fluid size instead of a whole scale?
Use the clamp() font-size generator — you give it a min px, max px, and viewport range and it returns one clamp() expression. The scale builder is for the whole ramp; the clamp generator is for one value with exact control over its viewport bounds.
Can I run this in a build instead of the web page?
Yes. GET /api/v1/tools/typography-scale-builder returns the option schema; pair the JAD runner once and POST your options to http://127.0.0.1:9789/v1/tools/typography-scale-builder/run to get the same CSS. The API honours scaleOrigin and the fluid viewport bounds that the web form omits. The build-pipeline guide has the full pattern.
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.