Every typography tutorial recommends the Perfect Fourth or the Golden Ratio. Neither is the right answer for most products.
The ratio you pick determines the personality of your entire type system. Pick it wrong in hour one and you're adjusting font sizes for the rest of the project, trying to compensate for a decision that should have been structural.
What a modular scale actually is
A modular scale is a set of numbers that share a common ratio. Starting from a base size, each step multiplies by that ratio. The result: every size is mathematically related to every other.
The ratio controls how dramatic the jumps between levels are. A high ratio creates strong contrast between levels — headings dominate. A low ratio creates subtle contrast — hierarchy comes from weight and color, not size.
The decision by project type
There is no universally correct ratio. There is a correct ratio for a given context. Three contexts, three answers:
Portfolio, landing, marketing
Ratio: 1.333 — Perfect Fourth
The hierarchy speaks for itself. H1 commands the page at normal weight. You don't need color tricks or bold extremes to establish which element is primary — the scale does the work.
/* Perfect Fourth — 1.333 · base 16px */
:root {
--text-sm: 0.750rem; /* 12px — metadata, labels */
--text-base: 1.000rem; /* 16px — body */
--text-lg: 1.333rem; /* 21px — lead */
--text-xl: 1.777rem; /* 28px — H4 */
--text-2xl: 2.369rem; /* 38px — H3 */
--text-3xl: 3.157rem; /* 51px — H2 */
--text-4xl: 4.209rem; /* 67px — H1 */
--text-5xl: 5.610rem; /* 90px — Display */
}
Signal it's working: use normal weight on an H1. The hierarchy still reads. Signal it's wrong: the H1 dominates so much it crushes the body text on content-heavy pages. Drop to 1.250.
Product, app, SaaS
Ratio: 1.250 — Major Third
Content and structure share the page without competing. The headings have clear presence without dominating what they label.
/* Major Third — 1.250 · base 16px */
:root {
--text-sm: 0.800rem; /* 13px — captions, metadata */
--text-base: 1.000rem; /* 16px — body */
--text-lg: 1.250rem; /* 20px — lead */
--text-xl: 1.563rem; /* 25px — H4, card titles */
--text-2xl: 1.953rem; /* 31px — H3 */
--text-3xl: 2.441rem; /* 39px — H2 */
--text-4xl: 3.052rem; /* 49px — H1 */
}
Signal it's working: place an H2 next to a body paragraph. Both have weight. Neither crushes the other. Signal it's wrong: headings barely differentiate from body at small viewport. Drop to 1.200.
Dashboard, data, dense interfaces
Ratio: 1.200 — Minor Third (or 1.125 at 14px base)
The information is the protagonist. Typography is infrastructure. Any heading that draws more attention than the data it labels is a design error.
/* Minor Third — 1.200 · base 16px */
:root {
--text-sm: 0.833rem; /* 13px — table body */
--text-base: 1.000rem; /* 16px — body */
--text-lg: 1.200rem; /* 19px — section subtitles */
--text-xl: 1.440rem; /* 23px — card titles */
--text-2xl: 1.728rem; /* 28px — panel titles */
--text-3xl: 2.074rem; /* 33px — page H2 */
--text-4xl: 2.488rem; /* 40px — page H1 */
}
/* Maximum density — 1.125 · base 14px */
:root {
--text-base: 0.875rem; /* 14px */
--text-lg: 0.984rem; /* 16px */
--text-xl: 1.107rem; /* 18px */
--text-2xl: 1.246rem; /* 20px */
/* Hierarchy comes from weight + color, not size */
}
Line-height is where systems break
Every tutorial covers the ratio. Almost none cover what comes next: each level of the scale needs a different line-height.
The rule: as text gets larger, line-height decreases. A 72px headline with line-height: 1.5 looks like oversized body text. A 14px label with line-height: 1.1 is illegible.
:root {
--leading-none: 1.0; /* display, 60px+ */
--leading-tight: 1.1; /* H1, 40-60px */
--leading-snug: 1.2; /* H2, H3, 24-40px */
--leading-heading: 1.3; /* H4, lead, 18-24px */
--leading-normal: 1.5; /* body, 15-18px */
--leading-relaxed: 1.65; /* long-form articles */
}
h1 {
font-size: var(--text-4xl);
line-height: var(--leading-tight);
letter-spacing: -0.02em;
}
p {
font-size: var(--text-base);
line-height: var(--leading-normal);
max-width: 65ch;
}
Letter-spacing completes the system
The third axis most designers skip: as text gets larger, letter-spacing should decrease. As text gets smaller, it should increase.
Large headings with positive tracking look like advertising posters. Small uppercase labels without tracking look cramped.
:root {
--tracking-tightest: -0.04em; /* display, 72px+ */
--tracking-tighter: -0.02em; /* H1, 48px+ */
--tracking-tight: -0.01em; /* H2, 32px+ */
--tracking-normal: 0em; /* H3, body */
--tracking-wide: 0.02em; /* small text */
--tracking-wider: 0.06em; /* uppercase labels — minimum */
--tracking-widest: 0.12em; /* all-caps at very small sizes */
}
.label {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
}
Uppercase reduces the optical space between letters. Without letter-spacing, all-caps text looks cramped even when it isn't. 0.06em is the minimum. Linear, Stripe, and Vercel all do this.
A real example: the subscription system
At Vocento, the type system had to work across 15 brands simultaneously. That constraint made the decision clear immediately.
Each brand could have its own font choice — different editorial identities. But the scale, the line-heights, and the letter-spacing rules had to be identical across all of them. Otherwise, implementing a single component change would require 15 individual calibrations.
The solution was a two-layer token system:
/* Layer 1: Brand tokens — different per brand */
:root[data-brand="elpais"] {
--font-display: 'Unidad', serif;
--font-body: 'Tiempos Text', serif;
--color-accent: hsl(0 72% 42%);
}
:root[data-brand="marca"] {
--font-display: 'Druk', sans-serif;
--font-body: 'Graphik', sans-serif;
--color-accent: hsl(214 90% 44%);
}
/* Layer 2: Scale tokens — identical across all brands */
:root {
--text-base: 1rem;
--text-xl: 1.563rem;
--text-3xl: 2.441rem;
--text-4xl: 3.052rem;
--leading-tight: 1.1;
--leading-normal: 1.5;
--tracking-tighter: -0.02em;
--tracking-wider: 0.06em;
}
The brand changes what the type looks like. The system decides how it behaves.
The ratio nobody picks: 1.250
Not dramatic enough to feel like a design decision. Not as defensible as "I used the Golden Ratio."
But it's the ratio behind most interfaces you consider well-designed without being able to say why. The ones where the information feels accessible and the hierarchy feels clear without the typography being the thing you notice.
The best type systems are invisible. The right ratio is usually the one that makes you forget there's a ratio at all.