X Xerobit

Fluid Typography with CSS clamp() — Scale Fonts Without Media Queries

CSS clamp() creates fluid typography that scales smoothly between minimum and maximum font sizes without media queries. Here's how to write fluid type scales, calculate clamp()...

Mian Ali Khalid · · 5 min read
Use the tool
Pixel to REM Converter
Convert px to rem, em, and percent — and vice versa. Configurable root font size. Bulk conversion mode for entire stylesheets.
Open Pixel to REM Converter →

clamp(min, preferred, max) sets a value that scales linearly between a minimum and maximum based on a viewport dimension. Applied to font-size, it creates fluid typography that’s always readable without media query breakpoints.

Use the PX to REM Converter to convert pixel values for your type scale.

clamp() syntax

font-size: clamp(minimum, preferred, maximum);
  • minimum: smallest possible font size
  • preferred: a viewport-relative expression that scales with screen width
  • maximum: largest possible font size
h1 {
  font-size: clamp(1.75rem, 4vw, 3rem);
  /*         28px   scales  48px */
}

At viewport widths:

  • Narrow (320px): 4vw = 12.8px → uses minimum 28px
  • Medium (700px): 4vw = 28px → uses preferred ~28px
  • Wide (1200px): 4vw = 48px → uses maximum 48px

Calculating the preferred expression

The preferred value should hit the minimum at the minimum viewport and the maximum at the maximum viewport:

Given:
  min-size: 18px = 1.125rem
  max-size: 28px = 1.75rem
  min-viewport: 320px
  max-viewport: 1200px

slope = (max-size - min-size) / (max-viewport - min-viewport)
      = (28 - 18) / (1200 - 320)
      = 10 / 880
      = 0.01136

intercept = min-size - slope × min-viewport
          = 18 - 0.01136 × 320
          = 18 - 3.636
          = 14.364px = 0.898rem

preferred = 0.01136 × 100vw + 0.898rem
          = 1.136vw + 0.898rem

Result:

font-size: clamp(1.125rem, 1.136vw + 0.898rem, 1.75rem);

Complete fluid type scale

:root {
  /* Mobile → Desktop scale */
  --text-sm:   clamp(0.875rem, 0.8vw + 0.7rem,  1rem);
  --text-base: clamp(1rem,     1vw + 0.8rem,     1.25rem);
  --text-lg:   clamp(1.125rem, 1.25vw + 0.9rem,  1.5rem);
  --text-xl:   clamp(1.25rem,  1.5vw + 1rem,     1.75rem);
  --text-2xl:  clamp(1.5rem,   2vw + 1.1rem,     2.25rem);
  --text-3xl:  clamp(1.875rem, 3vw + 1.2rem,     3rem);
  --text-4xl:  clamp(2.25rem,  4vw + 1.4rem,     3.75rem);
  --text-5xl:  clamp(3rem,     6vw + 1.5rem,     5rem);
}

body { font-size: var(--text-base); }
h1   { font-size: var(--text-4xl); }
h2   { font-size: var(--text-3xl); }
h3   { font-size: var(--text-2xl); }
h4   { font-size: var(--text-xl); }
h5   { font-size: var(--text-lg); }
small{ font-size: var(--text-sm); }

Tools for generating clamp() values

Rather than calculating manually, use these tools:

Utopia (utopia.fyi) — generates complete fluid type and space scales. Input your min/max viewport and min/max font sizes.

CSS Clamp Calculator — enter three sizes and two viewport widths, get the clamp() expression.

JavaScript formula:

function fluidClamp({ minSize, maxSize, minWidth = 320, maxWidth = 1200, unit = 'rem', base = 16 }) {
  const pxToRem = px => px / base;
  
  const slope = (maxSize - minSize) / (maxWidth - minWidth);
  const intercept = minSize - slope * minWidth;
  
  const minRem = pxToRem(minSize);
  const maxRem = pxToRem(maxSize);
  const slopeVw = slope * 100;
  const interceptRem = pxToRem(intercept);
  
  return `clamp(${minRem}rem, ${slopeVw.toFixed(4)}vw + ${interceptRem.toFixed(4)}rem, ${maxRem}rem)`;
}

fluidClamp({ minSize: 18, maxSize: 28 });
// "clamp(1.125rem, 1.1364vw + 0.8977rem, 1.75rem)"

Fluid spacing with clamp()

The same technique applies to padding, margin, and gap:

:root {
  --space-sm:  clamp(0.5rem,  1vw + 0.25rem, 1rem);
  --space-md:  clamp(1rem,    2vw + 0.5rem,  2rem);
  --space-lg:  clamp(1.5rem,  3vw + 0.75rem, 3rem);
  --space-xl:  clamp(2rem,    4vw + 1rem,    4rem);
}

section {
  padding: var(--space-xl) var(--space-md);
}

.card-grid {
  gap: var(--space-md);
}

Line height for fluid type

/* Tight line height for headings, looser for body: */
h1 {
  font-size: var(--text-4xl);
  line-height: 1.1;      /* tight */
  letter-spacing: -0.02em;
}

p {
  font-size: var(--text-base);
  line-height: 1.7;      /* looser for readability */
}

Testing fluid typography

// Check font sizes at various viewport widths:
const breakpoints = [320, 480, 768, 1024, 1440];

breakpoints.forEach(width => {
  // Simulate viewport width:
  window.innerWidth = width;
  const el = document.querySelector('h1');
  const size = window.getComputedStyle(el).fontSize;
  console.log(`${width}px → ${size}`);
});

Related posts

Related tool

Pixel to REM Converter

Convert px to rem, em, and percent — and vice versa. Configurable root font size. Bulk conversion mode for entire stylesheets.

Written by Mian Ali Khalid. Part of the Frontend & Design pillar.