Skip to content

Instantly share code, notes, and snippets.

@jh3y
Created May 14, 2026 22:16
Show Gist options
  • Select an option

  • Save jh3y/d850bd3d04c53e77e900750b65fc2b69 to your computer and use it in GitHub Desktop.

Select an option

Save jh3y/d850bd3d04c53e77e900750b65fc2b69 to your computer and use it in GitHub Desktop.
fluid typography scales with css pow()
/*
* Fluid Modular Type Scale
*
* A pure-CSS fluid typography system that smoothly scales font sizes
* between a minimum and maximum viewport width using a modular scale.
*
* Based on the great work of those at Clearleft, modified for modern css (pow())
* (https://utopia.fyi/blog/css-modular-scales/)
*
* Required custom properties (set on a parent or :root):
* --font-size-min : base font size at small viewports (unitless px, e.g. 16)
* --font-size-max : base font size at large viewports (unitless px, e.g. 20)
* --font-ratio-min : type scale ratio at small viewports (e.g. 1.2 — minor third)
* --font-ratio-max : type scale ratio at large viewports (e.g. 1.333 — perfect fourth)
* --font-width-min : viewport width where scaling starts (unitless px, e.g. 320)
* --font-width-max : viewport width where scaling stops (unitless px, e.g. 1440)
*
* Per-element custom property:
* --font-level : step in the scale (0 = body, 1 = one step up, 2 = two, etc.)
* --variable-unit : viewport unit for interpolation (defaults to 100vi;
* set to 100cqi for container-query-based fluid type)
*/
:where(.fluid) {
/* ---- Step 1: Compute the min and max font sizes for this level ----
*
* Raise the scale ratio to the power of the current level.
* Level 0 → pow(ratio, 0) = 1, so you get the base size.
* Level 1 → one step up the scale, level 2 → two steps, etc.
*
* Using different ratios for min/max means the hierarchy is compressed
* on small viewports and more dramatic on large ones.
*/
--fluid-min: calc(
var(--font-size-min) * pow(var(--font-ratio-min), var(--font-level, 0))
);
--fluid-max: calc(
var(--font-size-max) * pow(var(--font-ratio-max), var(--font-level, 0))
);
/* ---- Step 2: Calculate the slope (rate of change) ----
*
* This is the "m" in y = mx + b.
* It tells us how many px of font-size change per 1px of viewport width
* across the fluid range (font-width-min → font-width-max).
*/
--fluid-preferred: calc(
(var(--fluid-max) - var(--fluid-min)) /
(var(--font-width-max) - var(--font-width-min))
);
/* ---- Step 3: Build the clamp(min, preferred, max) ----
*
* clamp() ensures the font size never goes below min or above max.
* The middle (preferred) value is a linear interpolation: y = mx + b
*
* - m = --fluid-preferred (slope, from step 2)
* - x = the viewport unit (100vi by default, swappable to 100cqi)
* - b = the y-intercept: the rem offset that anchors the line so it
* hits --fluid-min exactly at --font-width-min
*
* The / 16 * 1rem conversions turn unitless-px values into rem,
* assuming 1rem = 16px (browser default).
*/
--fluid-type: clamp(
(var(--fluid-min) / 16) * 1rem,
((var(--fluid-min) / 16) * 1rem) -
(((var(--fluid-preferred) * var(--font-width-min)) / 16) * 1rem) +
(var(--fluid-preferred) * var(--variable-unit, 100vi)),
(var(--fluid-max) / 16) * 1rem
);
font-size: var(--fluid-type);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment