[ ]Relogic
Typography as UX: Variable Fonts, Fluid Sizing & Hierarchy
TypographyCSSDesign SystemsUX

Typography as UX: Variable Fonts, Fluid Sizing & Hierarchy

Great typography isn't decoration — it's architecture. A practical guide to building a typographic system that communicates and converts.

Typography accounts for 95% of web design. That's not an exaggeration — most of what users consume on a webpage is text. Yet it's the most consistently under-engineered part of most projects. Let's fix that.

Variable Fonts: One File, Infinite Possibilities

Traditional fonts ship one file per weight. A typical setup: Regular, Medium, SemiBold, Bold, ExtraBold — five network requests. Variable fonts ship a single file with a continuous axis from thin to black, animated in real-time by CSS.

/* Load once, use everywhere */
@font-face {
  font-family: "Inter";
  src: url("/fonts/Inter-variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}
 
/* Animate between weights on interaction */
.heading:hover {
  font-variation-settings: "wght" 800;
  transition: font-variation-settings 0.3s ease;
}

With GSAP, you can scrub font weight on scroll for kinetic typographic effects:

gsap.to(".display-text", {
  fontVariationSettings: '"wght" 800',
  scrollTrigger: {
    trigger: ".section",
    start: "top bottom",
    end: "top top",
    scrub: true,
  }
});

Fluid Typography with clamp()

Fixed px font sizes break responsiveness. Percentage sizes inherit unpredictably. vw scales too aggressively on ultrawide screens. The answer is clamp():

/* Minimum 1rem, preferred 2.5vw, maximum 1.25rem */
font-size: clamp(1rem, 2.5vw, 1.25rem);
 
/* A complete fluid type scale */
:root {
  --text-sm:      clamp(0.75rem, 1.5vw, 0.875rem);
  --text-base:    clamp(1rem, 2vw, 1.125rem);
  --text-lg:      clamp(1.125rem, 2.5vw, 1.25rem);
  --text-xl:      clamp(1.25rem, 3vw, 1.5rem);
  --text-2xl:     clamp(1.5rem, 4vw, 2rem);
  --text-display: clamp(2.5rem, 7vw, 5rem);
  --text-hero:    clamp(3rem, 10vw, 8rem);
}

This gives you perfect scaling across all screen sizes with zero media queries for font sizing.

Building a Type Hierarchy

A hierarchy has exactly one visual priority at each level. The common mistake is creating 6–8 heading levels that all look nearly identical. Instead:

Display (hero text)  → 72–96px, Extra Bold, tight leading
H1                   → 48–64px, Bold, tight leading
H2                   → 32–48px, Bold, normal leading
H3                   → 22–28px, SemiBold
Body large           → 18–20px, Regular, relaxed leading
Body                 → 16px, Regular, 1.6 line height
Caption / Label      → 12–14px, Medium, wide letter-spacing

Each level should be immediately distinguishable without needing to read the content.

Optical Sizing and Tracking

Large text needs tighter letter-spacing. Small text needs looser. This isn't a design preference — it's physics. At large sizes, the gaps between letters read as large voids. At small sizes, letters crowd together.

/* Display text — tight tracking */
.display { letter-spacing: -0.04em; }
 
/* Heading — slightly tight */
.heading { letter-spacing: -0.02em; }
 
/* Body — neutral */
.body { letter-spacing: 0; }
 
/* Caption/Label — loose */
.label { letter-spacing: 0.08em; }

Leading (Line Height) Principles

  • Display text: 0.9–0.95 — tight for visual impact
  • Headings: 1.0–1.2 — slightly more breathing room
  • Body text: 1.5–1.7 — generous for readability
  • Small/captions: 1.3–1.4 — compact but readable
// Tailwind classes that follow these principles
const typeScale = {
  display: "font-display text-[clamp(3rem,10vw,9rem)] font-extrabold leading-[0.9] tracking-tight",
  h1: "font-display text-[clamp(2.5rem,6vw,5rem)] font-bold leading-[0.95] tracking-tight",
  h2: "font-display text-[clamp(1.8rem,3.5vw,3rem)] font-bold leading-tight tracking-tight",
  body: "text-base leading-relaxed text-muted-foreground",
  caption: "text-xs uppercase tracking-widest font-semibold text-muted-foreground",
};

Measure (Line Length)

Optimal reading comfort is 55–75 characters per line. max-width: 72ch on body copy is a reasonable default. Too wide, and eyes have trouble finding the next line. Too narrow, and the reading rhythm breaks with constant line returns.

.prose {
  max-width: 72ch;
}

Pairing Fonts Effectively

The classic approach: one serif or display font for headings (personality, brand), one sans-serif for body (legibility, neutrality). Some rules:

  1. Contrast, not conflict — different enough to be distinct, similar enough in proportions to coexist
  2. Limit to two — three fonts looks chaotic unless you're building a newspaper
  3. Use variable fonts — the weight axes count as typographic variety within one family

Common pairings we use:

  • Syne (display) + Inter (body) — our own stack
  • Fraunces + DM Sans — editorial, magazine feel
  • Playfair Display + Lato — sophisticated, trustworthy
  • Space Grotesk + Space Mono — technical, developer tools

Typography is invisible when done right — users read the content without noticing the letterforms. That invisibility is the goal. Build your type system as infrastructure, not decoration, and every page you ship will communicate with more clarity and authority.

R

Relogic Studio

Web development agency specialising in motion-driven digital experiences.