Codeskill

Learn to code, step by step

Fluid typography and spacing

Fluid typography and spacing: type and gaps that scale smoothly between a minimum and maximum instead of jumping at breakpoints. The tools are clamp(), viewport units used carefully, and logical properties for margins and padding that behave in any writing mode.

The problem with breakpoint-only type

A common pattern from the intro series: small font on mobile, larger font at 768px. That works, but the jump can feel abrupt on tablets and large phones. Fluid type grows continuously between sensible bounds.

clamp() in plain terms

clamp(min, preferred, max) picks the preferred value when it sits between min and max; otherwise it sticks to the nearest bound:

:root {
  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --text-lg: clamp(1.25rem, 1.1rem + 0.75vw, 1.75rem);
  --text-xl: clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
}

body {
  font-size: var(--text-base);
}

h1 {
  font-size: var(--text-xl);
}

The middle term is usually rem + vw: mostly fixed for accessibility (users who zoom still get rem-based scaling), with a gentle viewport contribution. Avoid clamp(12px, 4vw, 48px) with px at both ends unless you have a reason – rem respects user font settings.

Building a type scale

Define a small set of steps and reuse them. Headings, lead paragraphs, and UI labels can share the same scale:

:root {
  --step--1: clamp(0.875rem, 0.85rem + 0.1vw, 0.9375rem);
  --step-0: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --step-1: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
  --step-2: clamp(1.5625rem, 1.35rem + 1vw, 2rem);
  --step-3: clamp(1.953rem, 1.6rem + 1.75vw, 2.5rem);
}

.lead {
  font-size: var(--step-1);
  line-height: 1.4;
}

small, .text-sm {
  font-size: var(--step--1);
}

Fluid spacing

Section gaps and stack spacing benefit from the same approach:

:root {
  --space-section: clamp(3rem, 2rem + 4vw, 6rem);
  --space-stack: clamp(1rem, 0.75rem + 1vw, 1.5rem);
}

.section + .section {
  margin-block-start: var(--space-section);
}

.prose > * + * {
  margin-block-start: var(--space-stack);
}

The * + * selector (lobotomised owl) adds space between siblings without margin on the first child. Pair it with logical margin-block so vertical writing modes behave correctly.

Logical properties recap

Physical properties (margin-top, padding-left, width) assume horizontal left-to-right layout. Logical properties follow the writing mode:

  • margin-block – top and bottom in horizontal writing (start/end in vertical)
  • margin-inline – left and right in horizontal writing
  • padding-block, padding-inline – same idea for padding
  • inline-size – width in horizontal writing
  • block-size – height in horizontal writing
.callout {
  padding-block: var(--space-stack);
  padding-inline: clamp(1rem, 0.5rem + 2vw, 2rem);
  margin-block-end: var(--space-section);
  max-inline-size: 65ch;
}

We go deeper on logical properties and writing modes in a later tutorial. For fluid layouts, start using block/inline in new code even if the site is English-only today.

Line length and ch units

Readable prose wants a line length around 45-75 characters. max-inline-size: 65ch caps width by character count, which tracks better than a fixed pixel width when font size is fluid.

.prose {
  font-size: var(--step-0);
  line-height: 1.6;
  max-inline-size: 65ch;
}

When to keep breakpoints

Fluid values do not replace every media query. Navigation that becomes a drawer at 768px is a discrete layout change, not a gradual scale. Use fluid type and space for continuous scaling; use breakpoints for structural swaps.

A practical mix: fluid heading sizes and section gaps everywhere, plus one or two breakpoints for grid columns and nav behaviour.

Testing fluid scales

Resize the browser slowly from 320px to 1400px. Text should grow smoothly without sudden jumps or unreadably small/large extremes. Check at 200% browser zoom – rem-based mins and maxes should still hold up.

Tools like utopia.fyi or clamp calculators help generate middle terms if maths is not your favourite pastime. Understand the output enough to tweak it; do not paste and forget.

The next tutorial goes beyond basic Grid: named areas, implicit tracks, and subgrid where it earns its keep.

PreviousContainer queries