Fluid type grows steadily from a minimum size at a small viewport to a maximum at a large one. clamp(min, preferred, max) (Numerical Functions) holds the limits; the preferred value is the line through the end points: slope = (max size − min size) / (max viewport − min viewport); intercept = min size − slope × min viewport; preferred = intercept in rem + slope × 100 in vw. For an h1 from 2rem at 20rem (320px) to 3rem at 80rem (1,280px), the slope is 1/60 and the intercept 1.6667rem; at 800px that gives 26.67px + 13.33px = 40px, exactly halfway.
| Role | At 320px | At 1,280px | Value |
|---|---|---|---|
| Body | 1rem (16px) | 1.125rem (18px) | clamp(1rem, 0.9583rem + 0.2083vw, 1.125rem) |
| h2 | 1.5rem (24px) | 2rem (32px) | clamp(1.5rem, 1.3333rem + 0.8333vw, 2rem) |
| h1 | 2rem (32px) | 3rem (48px) | clamp(2rem, 1.6667rem + 1.6667vw, 3rem) |
The free Utopia calculator (utopia.fyi (https://utopia.fyi/type/calculator/)) generates whole scales. Always keep the rem term: zoom shrinks the viewport in CSS pixels, so vw-only text never grows, a WCAG 1.4.4 failure. Test at 200% zoom, and inside components use cqi instead of vw (Container Queries).