Created
May 14, 2026 22:16
-
-
Save jh3y/d850bd3d04c53e77e900750b65fc2b69 to your computer and use it in GitHub Desktop.
fluid typography scales with css pow()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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