Type something to search...
How to Build a Modular Type Scale for Consistent Hierarchy

How to Build a Modular Type Scale for Consistent Hierarchy

Look through the CSS of a project that's been around for a few years and count the distinct font sizes. It's common to find a dozen or more: 13px, 14px, 15px, 17px, 22px, 23px, 28px, 30px. Each was chosen for a good reason in the moment, but together they produce a page where headings don't quite line up into a clear hierarchy and nothing feels related.

A modular type scale fixes this by generating every font size from two numbers: a base size and a ratio. Each step is the previous one multiplied by the ratio. The result is a small, consistent set of sizes that relate to each other in a predictable way, which makes hierarchy clearer and decisions faster.

This guide covers how modular scales work, how to choose a ratio, how to turn the scale into CSS custom properties, and how to make it fluid so it adapts from phones to wide screens.

What a Modular Scale Is

A modular scale is a geometric sequence. You start with a base, usually your body text size, and multiply by a fixed ratio to get the next step up. Divide by the ratio to go down.

With a base of 16px and a ratio of 1.25:

StepCalculationSize (px)Size (rem)
-216 ÷ 1.25²10.240.64
-116 ÷ 1.2512.80.8
016161
116 × 1.25201.25
216 × 1.25²251.563
316 × 1.25³31.251.953
416 × 1.25⁴39.062.441
516 × 1.25⁵48.833.052

Each step is 25% bigger than the one before. Because the relationship is constant, the jumps feel even to the eye, even though the absolute differences grow as sizes get larger. That matches how we perceive size: the difference between 12px and 16px is obvious, while 60px and 64px look almost the same.

Why Use a Scale at All

A scale isn't magic, and the ratio itself has no special power over readers. Its value is practical:

  • Fewer decisions. Instead of choosing between 22px and 23px, you choose between step 2 and step 3.
  • Clear contrast between levels. Every step is a meaningful change in size, so adjacent heading levels never look accidentally similar.
  • Consistency across a team. Designers and developers share a vocabulary. "Use step 3" is unambiguous.
  • Easier global changes. Change the base or ratio, and the whole hierarchy updates in proportion.

The scale gives you a structure. You still choose which step goes where, and you can break the scale when a specific case needs it.

Choosing a Ratio

Many scale tools label their ratios with names borrowed from musical intervals. The names are just labels. What matters is how much contrast you want between steps.

RatioCommon nameCharacterGood for
1.125Major secondVery subtle stepsDense UIs, dashboards, apps
1.2Minor thirdGentle, versatileDocumentation, apps, mobile
1.25Major thirdClear but restrainedBlogs, general-purpose sites
1.333Perfect fourthStrong contrastEditorial, marketing on desktop
1.5Perfect fifthDramatic, big headings fastLanding pages, posters
1.618Golden ratioVery dramaticDisplay-heavy layouts, rarely UI

Here's how quickly different ratios grow from a 16px base:

Step1.1251.21.251.3331.5
01616161616
11819.22021.3324
220.2523.042528.4336
322.7827.6531.2537.954
425.6333.1839.0650.5281
528.8339.8148.8367.34121.5

A few guidelines for choosing:

  • How many levels do you need? If you need six distinct heading levels plus several small sizes, a large ratio runs out of room fast. A 1.5 ratio puts step 5 at 121.5px.
  • How much screen do you have? Large ratios look great on a wide desktop and overwhelm a phone. This is the main reason to make the scale fluid, which is covered below.
  • What kind of content is it? Dense interfaces need smaller steps so labels, buttons, and data can differ subtly. Editorial pages can afford big, dramatic headlines.

If you're unsure, start with 1.2 or 1.25. They work for most sites.

Choosing the Base Size

The base is step 0, and it's almost always your body text size. For most sites, that means 1rem, which is 16px in the default browser settings. Some long-form sites use 1.125rem (18px) or larger for more comfortable reading.

Define the base in rem so it respects the user's browser font size preference. If someone sets their default to 20px, your whole scale grows with it. The guide on respecting user font size preferences explains why that matters.

Turning the Scale into CSS

The cleanest approach is a set of custom properties, one per step, with rounded rem values:

:root {
  --step--2: 0.64rem;
  --step--1: 0.8rem;
  --step-0: 1rem;
  --step-1: 1.25rem;
  --step-2: 1.563rem;
  --step-3: 1.953rem;
  --step-4: 2.441rem;
  --step-5: 3.052rem;
}

body {
  font-size: var(--step-0);
}

h1 {
  font-size: var(--step-5);
}

h2 {
  font-size: var(--step-4);
}

h3 {
  font-size: var(--step-3);
}

h4 {
  font-size: var(--step-2);
}

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

Naming the properties by step number rather than by element (--step-3 rather than --h3-size) keeps the scale independent of how you use it. A card title might use --step-2 without being an h4.

Calculating Steps in CSS

Recent browsers support the pow() function, so you can compute steps from a base and ratio directly:

:root {
  --ratio: 1.25;
  --base: 1rem;
  --step-1: calc(var(--base) * var(--ratio));
  --step-2: calc(var(--base) * pow(var(--ratio), 2));
  --step-3: calc(var(--base) * pow(var(--ratio), 3));
  --step-4: calc(var(--base) * pow(var(--ratio), 4));
  --step-5: calc(var(--base) * pow(var(--ratio), 5));
  --step--1: calc(var(--base) / var(--ratio));
}

This makes experimenting easy: change --ratio and the whole scale updates. Check the MDN page for pow() for current browser support before relying on it. If you need older browsers, use the precomputed values, or use repeated multiplication with calc(), which works everywhere.

Generating the Scale with JavaScript

If your design tokens live in JSON or a build script, generate the values once:

function modularScale({ base = 1, ratio = 1.25, min = -2, max = 5 }) {
  const steps = {};
  for (let i = min; i <= max; i++) {
    const value = base * Math.pow(ratio, i);
    steps[`step-${i}`] = `${Number(value.toFixed(3))}rem`;
  }
  return steps;
}

console.log(modularScale({ ratio: 1.2 }));

Rounding to three decimals keeps the output readable without introducing visible errors.

Making the Scale Fluid

A ratio that looks right on a 1440px screen is usually too dramatic on a 375px phone. The headline that spans a desktop hero at 50px wraps into four or five lines on mobile.

The fix is to use two scales: a smaller base and ratio for narrow screens, a larger base and ratio for wide ones, and interpolate between them with clamp(). For example:

  • Mobile (320px): base 16px, ratio 1.2
  • Desktop (1280px): base 18px, ratio 1.333

Each step gets a minimum from the mobile scale, a maximum from the desktop scale, and a preferred value that grows linearly with the viewport:

:root {
  --step--1: clamp(0.83rem, 0.83rem + 0.02vw, 0.84rem);
  --step-0: clamp(1rem, 0.958rem + 0.208vw, 1.125rem);
  --step-1: clamp(1.2rem, 1.1rem + 0.499vw, 1.5rem);
  --step-2: clamp(1.44rem, 1.254rem + 0.932vw, 2rem);
  --step-3: clamp(1.728rem, 1.416rem + 1.561vw, 2.665rem);
  --step-4: clamp(2.074rem, 1.581rem + 2.464vw, 3.552rem);
}

Small steps barely change, while large headings grow significantly. That's exactly what you want: body text stays comfortable everywhere, and headings get room to breathe on big screens.

Because each preferred value includes a rem component, the text still responds to browser zoom and font size preferences. A pure vw value would not. The full method for calculating these values is covered in how to build fluid typography.

Mapping the Scale to Elements

A scale gives you sizes, not a hierarchy. You still need to decide which step each element uses, and size is only one part of that. Weight, color, case, and spacing all contribute, as the guide on creating visual hierarchy with size, weight, and color explains.

A typical mapping for an article page:

ElementStepNotes
Page title (h1)5Often the only use of the top step
Section (h2)3Skipping a step makes sections stand out
Subsection (h3)2Close to body size, so weight does more work
Lead paragraph1Slightly larger than body
Body0The base
Captions, meta-1Never below about 12px equivalent

You don't need to use every step, and you don't need to assign them in strict order. Skipping a step between h1 and h2 often produces a clearer hierarchy than using adjacent steps.

Line Height Across the Scale

Larger text needs proportionally less line height. A simple approach is to set line height per step range:

body {
  line-height: 1.6;
}

h1,
h2 {
  line-height: 1.1;
}

h3,
h4 {
  line-height: 1.25;
}

Another option is a calculated line height that tightens as size grows, such as line-height: calc(1em + 0.5rem). At a 16px body size it gives 1.5; at 48px it gives about 1.17.

When to Break the Scale

A scale is a tool, not a law. Common reasons to step outside it:

  • A single hero headline that needs to be much larger than anything else on the site.
  • Fonts with unusual proportions. A typeface with a small x-height may need body text slightly larger than your base.
  • UI components with fixed constraints, like a tiny badge or a dense table cell.

When you break the scale, do it deliberately and document why. If you find yourself adding many exceptions, your ratio may be wrong for the content.

Common Mistakes

  • Using a large ratio on mobile. A 1.5 or golden-ratio scale makes phone headlines enormous. Use a smaller ratio for narrow screens.
  • Defining too many steps. Most sites need around six to eight sizes. More than that and you've rebuilt the random sizes you started with.
  • Using px for the scale. Pixel values ignore user font size settings. Use rem.
  • Pure viewport units in fluid scales. font-size: 4vw doesn't respond to zoom properly. Always include a rem component inside clamp().
  • Treating the scale as hierarchy. Size alone doesn't create hierarchy. Pair it with weight, color, and spacing.
  • Over-precise values. Sizes like 1.953125rem add nothing. Round to two or three decimals.

Conclusion

A modular type scale replaces dozens of arbitrary font sizes with a handful of related ones, generated from a base size and a ratio. Smaller ratios like 1.2 suit dense interfaces and mobile; larger ones like 1.333 suit editorial and desktop layouts. Store the steps as custom properties named by step number, and they become a shared vocabulary across design and code.

To put it into practice, start with a 1rem base and a ratio of 1.2 or 1.25, map the steps to your elements, and test with real content. Then make it fluid with clamp() so small screens get a gentler scale and large screens get more contrast. Once it's in place, you'll rarely need to invent a new font size again.

Share :

Related Posts

A Short History of Typography: From Gutenberg to Variable Fonts

A Short History of Typography: From Gutenberg to Variable Fonts

When you write font-weight: 600 or font-style: italic, you're using vocabulary that is hundreds of years old. "Italic" comes from Renaissance Ita

Continue Reading
Accessible Typography: Font Size, Contrast, and Spacing Guidelines

Accessible Typography: Font Size, Contrast, and Spacing Guidelines

Most accessibility work on a website involves text. Navigation labels, form instructions, error messages, article bodies, button captions: if people

Continue Reading
Bauhaus Typography and Its Influence on Modern Design

Bauhaus Typography and Its Influence on Modern Design

Bold sans-serif type, thick black bars, red accents, text running at an angle, and lowercase-only headlines. If a poster or website uses that combina

Continue Reading