Typography

Type Scale Systems

A type scale is not a list of font sizes — it is a relationship. Choosing the right ratio, encoding it as tokens, and making it fluid with clamp() creates typography that is harmonious at every viewport, maintainable across every component.

7
Scale Steps
1.25
Ratio
0
Breakpoints Needed
01 / Foundation

A system of sizes with a common ratio

A type scale is a set of font sizes derived from a single base size and a consistent multiplier — the ratio. Starting from a base of 16 px and multiplying repeatedly by 1.25 gives you 20, 25, 31.25, 39.06, and so on. Each step is mathematically related to the others, which is what makes the resulting text feel harmonious without any manual tuning.

The term comes from music: a major scale has predictable intervals between notes, and a type scale has predictable intervals between sizes. Picking arbitrary pixel values — 14, 17, 22, 28 — breaks those intervals and forces the eye to do extra work interpreting hierarchy. A well-chosen ratio does that work for you.

The six most common ratios each have a name borrowed from music theory: Minor Second (1.067), Major Second (1.125), Minor Third (1.2), Major Third (1.25), Perfect Fourth (1.333), and Augmented Fourth (1.414). Smaller ratios create subtle, compact hierarchies suited to dense UIs. Larger ratios produce dramatic, editorial contrasts suited to landing pages and marketing.

--font-size-body-xs 12px · 0.75rem
Labels, captions, metadata
--font-size-body-sm 14px · 0.875rem
Secondary body copy
--font-size-body 16px · 1rem
Primary body text
--font-size-body-lg 20px · 1.25rem
Lead paragraphs
--font-size-h4 24px · 1.5rem
Section subheading
--font-size-h3 32px · 2rem
Article heading
--font-size-h2 40px · 2.5rem
Page heading
--font-size-h1 56px · 3.5rem
Hero title
02 / Ratios

Picking the right multiplier

The ratio is the single most important decision in a type scale. It controls how much contrast exists between adjacent steps. A ratio that is too small produces sizes that look nearly identical, making hierarchy invisible. A ratio that is too large produces extreme jumps that leave gaps in the scale — you end up with no usable size for body text, subheadings, and page titles simultaneously.

A good rule of thumb: use a smaller ratio (1.125–1.2) for product UIs with dense information — data tables, admin dashboards, developer tools. Use a larger ratio (1.25–1.333) for content-focused interfaces — blogs, marketing sites, portfolios. The Major Third at 1.25 sits at the intersection: dramatic enough to establish clear hierarchy, compact enough to cover all semantic levels without running into absurdly large display sizes.

Minor Third 1.200

Compact hierarchy. Good for dense product UIs where subtle differentiation is enough.

Major Third 1.250

Balanced contrast. The sweet spot for most interfaces — clear hierarchy without dramatic jumps.

Perfect Fourth 1.333

Editorial contrast. Dramatic hierarchy for landing pages and content-first experiences.

03 / Tokens

Encoding the scale as custom properties

A type scale only becomes a system when the sizes are encoded as design tokens — CSS custom properties that are defined once and consumed everywhere. Hard-coding font-size: 24px in a component breaks the connection to the scale; using font-size: var(--font-size-h4) means the component inherits any future scale adjustment automatically.

Token naming should encode semantic role, not raw value. Prefer --font-size-h2 over --font-size-40: the first name survives a ratio change, the second becomes a lie the moment you update the value. If you need both semantic and numeric names, map one to the other rather than defining both independently.

css
/* tokens.css — base values, static (no fluid yet) */
:root {
  /* Base */
  --font-size-base:     1rem;       /* 16px */
  --type-scale-ratio:  1.25;      /* Major Third */

  /* Body steps */
  --font-size-body-xs:  0.75rem;    /* 12px */
  --font-size-body-sm:  0.875rem;   /* 14px */
  --font-size-body:     1rem;       /* 16px */
  --font-size-body-lg:  1.25rem;    /* 20px */

  /* Heading steps */
  --font-size-h4:       1.5rem;     /* 24px */
  --font-size-h3:       2rem;       /* 32px */
  --font-size-h2:       2.5rem;     /* 40px */
  --font-size-h1:       3.5rem;     /* 56px */
}
Token Value Role Preview
--font-size-body-xs 0.75rem · 12px Labels, tags, captions Aa
--font-size-body-sm 0.875rem · 14px Secondary text, metadata Aa
--font-size-body 1rem · 16px Primary body copy Aa
--font-size-body-lg 1.25rem · 20px Lead paragraphs, intros Aa
--font-size-h4 1.5rem · 24px Card title, section label Aa
--font-size-h3 2rem · 32px Article subheading Aa
--font-size-h2 2.5rem · 40px Page section heading Aa
--font-size-h1 3.5rem · 56px Hero / page title Aa
04 / Fluid

Making the scale respond to the viewport

Static tokens give you a consistent scale, but on a 320 px phone a heading at 40 px can overflow its container, and on a 2560 px monitor 16 px body text becomes uncomfortably small. The solution is to replace fixed rem values with clamp() expressions that scale proportionally between a minimum at 320 px and a maximum at 1280 px.

The fluid preferred value can be derived mechanically: given that you want minSize at minVW and maxSize at maxVW, the slope is (maxSize − minSize) / (maxVW − minVW) and the intercept is minSize − slope × minVW. Written as a calc(): intercept + slope × 100vw.

In practice you rarely compute this by hand — a tool like clamp.font-size.app generates the expression for any min/max pair. What matters is understanding the structure so you can adjust the endpoints intentionally when the scale needs to shift for a specific design context.

css
/* Fluid type scale — each token scales between 320px and 1280px */
:root {
  /* Body steps stay mostly static — legibility floors dominate */
  --font-size-body-xs:  0.75rem;
  --font-size-body-sm:  0.875rem;
  --font-size-body:     1rem;
  --font-size-body-lg:  clamp(1.125rem, 0.9rem + 0.5vw, 1.25rem);

  /* Heading steps — fluid from mobile to desktop */
  --font-size-h4:  clamp(1.25rem,  1rem  + 0.8vw,  1.5rem);
  --font-size-h3:  clamp(1.5rem,   1rem  + 1.5vw,  2rem);
  --font-size-h2:  clamp(1.75rem,  0.8rem + 2.5vw,  2.5rem);
  --font-size-h1:  clamp(2rem,     0.5rem + 4vw,    3.5rem);
}

Notice that body steps use static values or very gentle clamps — their legibility floor is the critical constraint, and aggressive scaling would make body text uncomfortable on mid-size viewports. The fluid range is concentrated in the heading steps where the visual benefit is highest and the risk of over-scaling is lower.

05 / Application

Using tokens consistently in components

Once tokens are defined, the rule is simple: every font-size declaration in a component must reference a token. Never write a raw size. This creates a single source of truth — updating a token propagates instantly across every component that uses it, without searching the codebase for font-size: 24px.

The semantic token names also serve as documentation. A developer reading font-size: var(--font-size-h4) immediately understands the intent. Reading font-size: 1.5rem requires mental decoding and provides no guarantee the value is on-scale.

css
/* card.css — all sizes from the scale */
.card__eyebrow {
  font-size:   var(--font-size-body-xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.1em;
}

.card__title {
  font-size:   var(--font-size-h4);
  line-height: var(--line-height-tight);
}

.card__body {
  font-size:   var(--font-size-body-sm);
  line-height: var(--line-height-relaxed);
}

.page-hero__title {
  font-size:   var(--font-size-h1);
  line-height: var(--line-height-tight);
}

/* What NOT to do */
.card__title {
  font-size: 24px;  /* ✗ breaks the token contract */
  font-size: 1.5rem; /* ✗ coincidentally on-scale but not enforceable */
}
06 / Takeaway

The system in three steps

A complete type scale system comes down to three decisions made once and enforced everywhere. First, choose a ratio that matches the visual density of your product — 1.25 is a safe default. Second, encode every step as a CSS custom property with a semantic name — --font-size-h2 not --font-size-40px. Third, make the heading steps fluid with clamp() so the scale responds to the viewport without breakpoints.

After that, the rule is mechanical: if a component needs a font-size, it uses a token. No exceptions. The scale pays compound interest — every component that follows the rule makes future refactors cheaper and the overall type hierarchy more coherent.