Perfect Web Typography: How to Build a Modular Type Scale

Perfect Web Typography: How to Build a Modular Type Scale

April 22, 2026
10 min read

Pick any design that feels polished and professional - the text sizes are almost certainly following a mathematical system. A modular type scale takes a single base size and a ratio, and generates every heading and body size automatically. The result is visual harmony that "just feels right." Here's how to build one from scratch.

1. What Is a Modular Type Scale (and Why Random Font Sizes Look Bad)

A modular type scale is a set of font sizes derived from a base size multiplied repeatedly by a fixed ratio. Every size in the scale has a mathematical relationship to every other size - creating visual rhythm and hierarchy that feels intentional.

When designers choose font sizes arbitrarily - say, 13px for captions, 17px for body, 22px for subheadings, 31px for headings - each size is correct in isolation but the spacing between sizes feels inconsistent. The visual step from body to subheading doesn't match the step from subheading to heading, creating subtle visual noise.

Arbitrary sizes (feels uneven)

Heading
Subheading
Body text paragraph goes here.
Caption text

Modular scale 1.333 (harmonious)

Heading
Subheading
Body text paragraph goes here.
Caption text

2. Popular Scale Ratios

The ratio determines how dramatic the size jumps are. Smaller ratios (1.067, 1.125) create subtle hierarchy - good for text-heavy apps. Larger ratios (1.414, 1.618) create bold, expressive headings - good for marketing sites.

Minor Second×1.067(Half-step in music)

Extremely subtle. Good for dense data UIs where hierarchy must be gentle.

AaAaAaAaAa

Approximate sizes: 15px → 16px → 17px → 18px → 19px

Major Second×1.125(Whole-step in music)

Soft, readable. Popular for long-form content sites and documentation.

AaAaAaAaAa

Approximate sizes: 14px → 16px → 18px → 20px → 23px

Minor Third×1.2(Three half-steps)

Balanced and versatile. The most common choice for web apps.

AaAaAaAaAa

Approximate sizes: 13px → 16px → 19px → 23px → 28px

Perfect Fourth×1.333(Five half-steps (4th interval))

Clear hierarchy, expressive headings. Great for marketing and landing pages.

AaAaAaAaAa

Approximate sizes: 12px → 16px → 21px → 28px → 38px

Golden Ratio×1.618(φ - the golden ratio)

Very dramatic jumps. Use sparingly - display headings stand out boldly.

AaAaAaAaAa

Approximate sizes: 10px → 16px → 26px → 42px → 67px

3. How to Calculate a Scale from a Base

The formula is simple. Given a base size b and a ratio r, the size at step n is:

size(n) = b × rn

n = 0 is your base size. Positive n = larger. Negative n = smaller.

/* Base: 16px | Ratio: Perfect Fourth (1.333) */

Step -2 = 16 × 1.333^(-2) = 16 ÷ 1.777 = 9.0px  → xs
Step -1 = 16 × 1.333^(-1) = 16 ÷ 1.333 = 12.0px → sm
Step  0 = 16 × 1.333^0    = 16 × 1     = 16.0px → base ← body text
Step +1 = 16 × 1.333^1    = 16 × 1.333 = 21.3px → lg
Step +2 = 16 × 1.333^2    = 16 × 1.777 = 28.4px → xl
Step +3 = 16 × 1.333^3    = 16 × 2.369 = 37.9px → 2xl
Step +4 = 16 × 1.333^4    = 16 × 3.157 = 50.5px → 3xl
Step +5 = 16 × 1.333^5    = 16 × 4.209 = 67.3px → 4xl
Step +6 = 16 × 1.333^6    = 16 × 5.610 = 89.8px → 5xl

In practice, you round to whole or half pixels. ColorPeek's Type Scale Generator does this calculation automatically - you pick your base and ratio, and it outputs a complete scale you can copy.

4. Perfect Fourth Scale: xs through 5xl

Base: 16px | Ratio: 1.333 (Perfect Fourth)

NameSteppxremPreviewUse Case
xs-29px0.563remAaFine print, legal text
sm-112px0.75remAaCaptions, labels, helper text
base+016px1remAaBody text, paragraphs
lg+121px1.333remAaLead paragraphs, large body
xl+228px1.777remAaH4, section subheadings
2xl+338px2.369remAaH3, card titles
3xl+451px3.157remAaH2, page section headings
4xl+568px4.209remAaH1, hero headings
5xl+690px5.61remAaDisplay, large hero

5. Implementing in CSS Custom Properties

Store your type scale as CSS custom properties in :root. This makes theme switching (larger/smaller base for accessibility) trivial.

/* Perfect Fourth scale - base 16px / ratio 1.333 */
:root {
  --font-size-xs:   0.563rem;   /*  ~9px  */
  --font-size-sm:   0.750rem;   /* ~12px  */
  --font-size-base: 1rem;       /*  16px  */
  --font-size-lg:   1.333rem;   /* ~21px  */
  --font-size-xl:   1.777rem;   /* ~28px  */
  --font-size-2xl:  2.369rem;   /* ~38px  */
  --font-size-3xl:  3.157rem;   /* ~51px  */
  --font-size-4xl:  4.209rem;   /* ~67px  */
  --font-size-5xl:  5.610rem;   /* ~90px  */
}

/* Usage */
body        { font-size: var(--font-size-base); }
h1          { font-size: var(--font-size-4xl); }
h2          { font-size: var(--font-size-3xl); }
h3          { font-size: var(--font-size-2xl); }
h4          { font-size: var(--font-size-xl);  }
.lead       { font-size: var(--font-size-lg);  }
.caption    { font-size: var(--font-size-sm);  }
.legal      { font-size: var(--font-size-xs);  }

/* Accessibility override - boost base for vision impairment */
@media (prefers-larger-text) {
  :root { font-size: 18px; } /* entire scale scales proportionally */
}

6. Implementing in Tailwind Config

Override Tailwind's default fontSize scale with your modular values. Each entry accepts a tuple of [size, lineHeight] for bundled line-height defaults.

// tailwind.config.js
module.exports = {
  theme: {
    fontSize: {
      xs:   ['0.563rem', { lineHeight: '1rem' }],
      sm:   ['0.750rem', { lineHeight: '1.25rem' }],
      base: ['1rem',     { lineHeight: '1.6rem' }],
      lg:   ['1.333rem', { lineHeight: '1.75rem' }],
      xl:   ['1.777rem', { lineHeight: '2rem' }],
      '2xl':['2.369rem', { lineHeight: '2.5rem' }],
      '3xl':['3.157rem', { lineHeight: '1.2' }],
      '4xl':['4.209rem', { lineHeight: '1.1' }],
      '5xl':['5.610rem', { lineHeight: '1' }],
    },
  },
};

/* Usage in JSX */
// <h1 className="text-4xl font-bold">Hero Heading</h1>
// <p className="text-base leading-relaxed">Body text...</p>
// <span className="text-xs text-[var(--text-muted)]">Caption</span>

Notice line-height values: smaller heading sizes need relative line-heights (1.2, 1.1, 1.0) while body text needs more generous spacing (1.5–1.6).

7. Responsive Type Scales with clamp()

The CSS clamp() function enables truly fluid typography - font sizes that smoothly scale between a minimum and maximum value based on viewport width, without any breakpoints.

/* clamp(min, preferred, max) */
/* preferred = viewport-relative value */

:root {
  /* H1: 28px on mobile → scales to 68px on 1280px viewport */
  --font-size-4xl: clamp(1.75rem, 2.5vw + 1rem, 4.25rem);

  /* H2: 22px mobile → 51px desktop */
  --font-size-3xl: clamp(1.375rem, 2vw + 0.875rem, 3.2rem);

  /* H3: 18px mobile → 38px desktop */
  --font-size-2xl: clamp(1.125rem, 1.5vw + 0.75rem, 2.375rem);

  /* Body: fixed at 1rem, no scaling needed */
  --font-size-base: 1rem;
}

/* The formula to calculate the preferred value:
   preferred = (maxSize - minSize) / (maxVW - minVW) × 100vw
             + (minSize - slope × minVW)

   Or use a tool like utopia.fyi or ColorPeek's Type Scale generator */

Pro tip: Only clamp headings, not body text

Body text (base, sm, xs) should stay fixed at their defined sizes for readability. Fluid scaling is most impactful on headings (xl and above) where the visual weight matters more on different screen sizes.

8. Pairing Type Scale with Line-Height and Letter-Spacing

Font size alone doesn't create great typography. Line-height and letter-spacing work together with font size to achieve optimal readability.

Size RoleLine HeightLetter SpacingWhy
Display / 5xl1.0–1.1-0.03em to -0.04emTight tracking at large sizes avoids gaps
H1 / 4xl1.1–1.2-0.02em to -0.03emHeadings need tight leading
H2–H3 / 2xl–3xl1.2–1.3-0.01em to -0.02emSlightly looser for readability
H4–Lead / lg–xl1.4–1.50emNeutral spacing
Body / base1.5–1.70em to 0.01emMaximum comfort for long reads
Caption / xs–sm1.4–1.50.01em to 0.02emSmall text needs looser tracking
/* Complete heading system */
h1 {
  font-size: var(--font-size-4xl);    /* 67px */
  line-height: 1.1;
  letter-spacing: -0.025em;
  font-weight: 800;
}

h2 {
  font-size: var(--font-size-3xl);    /* 51px */
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-weight: 700;
}

/* Body - optimized for long-form reading */
body {
  font-size: var(--font-size-base);   /* 16px */
  line-height: 1.65;
  letter-spacing: 0.01em;
  font-weight: 400;
}

/* Small print */
.caption {
  font-size: var(--font-size-xs);     /* 9px */
  line-height: 1.5;
  letter-spacing: 0.02em;
  font-weight: 500;                   /* slightly bolder compensates for small size */
}

Share this article

Generate Your Type Scale in Seconds

Pick a base size, choose a ratio, and instantly get a complete type scale with CSS variables, Tailwind config, and SCSS output - plus fluid clamp() values for responsive typography.