Type something to search...
What is a modular type scale and how do you create one?

What is a modular type scale and how do you create one?

A modular type scale is a set of font sizes created by starting from a base size and repeatedly multiplying or dividing it by the same ratio. With a 16px base and a ratio of 1.25, for example, the scale goes 16px, 20px, 25px, 31.25px, 39.06px and so on upwards, and 12.8px and 10.24px downwards. Because every step is the same proportion larger than the last, the sizes feel related, and the jumps between body text, subheadings and headings are consistent. Many common ratios are borrowed from musical intervals, such as the major third (1.25) and the perfect fourth (1.333). You create one by choosing a base, choosing a ratio, calculating the steps, and storing them as CSS custom properties, optionally making them fluid with clamp().

Without a scale, font sizes tend to be chosen one at a time: 18px here, 22px there, 28px for a heading someone thought looked about right. Over time a site ends up with a dozen sizes that are close to each other but not quite the same, and the hierarchy turns muddy. A modular scale replaces those ad hoc choices with a small, predictable system. In this article you'll learn how modular scales work, how to choose a base size and ratio, how to calculate and implement a scale in CSS, how to make it respond to screen size, how to use it in Tailwind CSS, and when to break the rules.

How a Modular Scale Works

A modular scale is a geometric sequence. Each step is the base size multiplied by the ratio raised to the power of the step number:

size(step) = base × ratio ^ step

Step 0 is the base, positive steps are larger and negative steps are smaller. With a base of 16px and a ratio of 1.25:

StepCalculationSize
-216 ÷ 1.25 ÷ 1.2510.24px
-116 ÷ 1.2512.8px
01616px
116 × 1.2520px
216 × 1.25²25px
316 × 1.25³31.25px
416 × 1.25⁴39.06px
516 × 1.25⁵48.83px

The gaps between steps grow as you go up: 4px from step 0 to 1, but almost 10px from step 4 to 5. That's intentional. Our perception of size differences is roughly proportional, so a fixed ratio makes each step look like the same amount of change.

Choose a Base Size

The base is usually your body text size, because body text is the most common and most important size on the page. In most cases that's 16px (1rem), the default in every major browser. Text-heavy sites often use a slightly larger base, such as 17px to 20px, for more comfortable reading.

Express the base in rem rather than pixels, so the whole scale respects the reader's browser font size setting. If someone sets their default to 20px, every step of a rem-based scale grows by the same proportion.

Choose a Ratio

The ratio controls how dramatic the hierarchy is. The common named ratios come from musical intervals and classical proportions:

RatioNameSteps from 16pxGood for
1.067Minor second16, 17.07, 18.22, 19.44Dense interfaces with many levels
1.125Major second16, 18, 20.25, 22.78Apps, dashboards, small screens
1.2Minor third16, 19.2, 23.04, 27.65General purpose, mobile
1.25Major third16, 20, 25, 31.25Blogs, documentation, most websites
1.333Perfect fourth16, 21.33, 28.43, 37.9Editorial and marketing pages
1.414Augmented fourth16, 22.62, 31.99, 45.23Bold editorial layouts
1.5Perfect fifth16, 24, 36, 54Landing pages, large displays
1.618Golden ratio16, 25.89, 41.89, 67.77Posters, very few levels

Some guidelines for choosing:

  • More levels need a smaller ratio: If you need six heading sizes, a large ratio makes the top step enormous. A dashboard might use 1.125; a landing page with only two heading levels can use 1.5.
  • Small screens need a smaller ratio: A 1.5 scale puts an h1 at 54px or more, which leaves room for only a few words per line on a phone.
  • Large ratios suit fewer, bolder headings: Editorial layouts with one big headline benefit from strong contrast.

If you're not sure, 1.25 is a sensible starting point for content sites, and 1.2 for interfaces.

Musical Names Are Just Labels

The names describe the frequency ratio between two notes, which is where the numbers come from. There's no evidence that a scale feels better because it matches a musical interval. Pick a ratio by how the sizes look with your font and content, not by its name.

Implement the Scale in CSS

Option 1: Precomputed Values

The most widely compatible approach is to calculate the values once and store them as custom properties. A short Node.js script generates them:

// scale.mjs
const base = 16;      // px
const ratio = 1.25;   // major third
const steps = ["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl"];
const baseIndex = steps.indexOf("base");

const lines = steps.map((name, i) => {
  const px = base * ratio ** (i - baseIndex);
  const rem = Math.round((px / 16) * 1000) / 1000;
  return `  --step-${name}: ${rem}rem; /* ${px.toFixed(2)}px */`;
});

console.log(`:root {\n${lines.join("\n")}\n}`);
node scale.mjs
:root {
  --step-xs: 0.64rem; /* 10.24px */
  --step-sm: 0.8rem; /* 12.80px */
  --step-base: 1rem; /* 16.00px */
  --step-lg: 1.25rem; /* 20.00px */
  --step-xl: 1.563rem; /* 25.00px */
  --step-2xl: 1.953rem; /* 31.25px */
  --step-3xl: 2.441rem; /* 39.06px */
  --step-4xl: 3.052rem; /* 48.83px */
}

Paste the output into your stylesheet and map elements to steps:

body       { font-size: var(--step-base); }
small,
.caption   { font-size: var(--step-sm); }
h4         { font-size: var(--step-lg); }
h3         { font-size: var(--step-xl); }
h2         { font-size: var(--step-2xl); }
h1         { font-size: var(--step-3xl); }
.display   { font-size: var(--step-4xl); }

Option 2: Calculate in CSS With pow()

CSS has a pow() function, supported in current versions of all major browsers, so you can let the browser calculate the scale. Changing the ratio then updates every size at once:

:root {
  --ratio: 1.25;
  --base: 1rem;

  --step--1: calc(var(--base) * pow(var(--ratio), -1));
  --step-0: var(--base);
  --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));
}

@media (max-width: 40rem) {
  :root {
    --ratio: 1.2;
  }
}

The media query switches to a gentler ratio on narrow screens. Because every step references --ratio, the whole scale tightens together. If you need to support older browsers that lack pow(), use precomputed values instead.

Make the Scale Fluid

Switching ratios at a breakpoint works, but sizes jump suddenly when the viewport crosses it. A fluid scale interpolates smoothly between a small-screen scale and a large-screen scale using clamp(). This is the approach popularised by the Utopia project.

Suppose you want:

  • At 320px wide: a 16px base with a 1.2 ratio.
  • At 1240px wide: an 18px base with a 1.25 ratio.

For each step you calculate the minimum size, the maximum size, and the linear slope between them. The results look like this:

:root {
  --step--1: clamp(0.8333rem, 0.8101rem + 0.1159vw, 0.9rem);
  --step-0: clamp(1rem, 0.9565rem + 0.2174vw, 1.125rem);
  --step-1: clamp(1.2rem, 1.1283rem + 0.3587vw, 1.4063rem);
  --step-2: clamp(1.44rem, 1.3295rem + 0.5527vw, 1.7578rem);
  --step-3: clamp(1.728rem, 1.5648rem + 0.8161vw, 2.1973rem);
  --step-4: clamp(2.0736rem, 1.8395rem + 1.1704vw, 2.7466rem);
}

Each value has three parts: the minimum size, a preferred size that grows with the viewport, and the maximum size. The preferred value combines a rem term with a vw term. Keeping a rem term in it matters for accessibility, because text sized purely in vw doesn't grow when users zoom with browser text-size settings, and WCAG 1.4.4 Resize Text requires that text can be resized up to 200 percent.

You can calculate these values with this function:

function fluidStep(step, { minW = 320, maxW = 1240, minBase = 16, maxBase = 18, minRatio = 1.2, maxRatio = 1.25 } = {}) {
  const min = minBase * minRatio ** step;
  const max = maxBase * maxRatio ** step;
  const slope = (max - min) / (maxW - minW);
  const intercept = min - slope * minW;
  const rem = (px) => `${(px / 16).toFixed(4)}rem`;
  return `clamp(${rem(min)}, ${rem(intercept)} + ${(slope * 100).toFixed(4)}vw, ${rem(max)})`;
}

console.log(fluidStep(2));
clamp(1.4400rem, 1.3295rem + 0.5527vw, 1.7578rem)

For more on the technique, see what is fluid typography and how do you build it with clamp().

Use a Modular Scale in Tailwind CSS

In Tailwind CSS v4, theme values are defined in CSS with the @theme directive. Font size utilities read from variables in the --text-* namespace, so you can replace the defaults with your scale:

@import "tailwindcss";

@theme {
  --text-*: initial;

  --text-sm: 0.8rem;
  --text-base: 1rem;
  --text-lg: 1.25rem;
  --text-xl: 1.563rem;
  --text-2xl: 1.953rem;
  --text-3xl: 2.441rem;
  --text-4xl: 3.052rem;
}

Setting --text-*: initial removes Tailwind's built-in sizes, so only your scale is available. Classes such as text-lg and text-3xl now use the modular values:

<h1 class="text-3xl font-bold leading-tight">A modular heading</h1>
<p class="text-base leading-relaxed">Body text at the base size.</p>

Pair the Scale With Line Heights

As text gets larger, it needs proportionally less line height. A common pattern is to decrease line height as you go up the scale:

body { line-height: 1.6; }
h4   { line-height: 1.4; }
h3   { line-height: 1.3; }
h2   { line-height: 1.2; }
h1   { line-height: 1.1; }

Some designers also tighten letter spacing slightly on the largest steps, since big text can look loosely spaced. Keep any adjustment small, such as -0.01em to -0.02em, and check it with your chosen font.

When to Break the Scale

A scale is a guide, not a law. Common, sensible exceptions include:

  • Rounding for crisp rendering: 39.06px can become 39px or 40px if it looks better.
  • Interface text: A button or form label may need a size between steps for the component to work.
  • Large display text: A one-off hero headline can go beyond the top step.
  • Different fonts: A heading font with a large x-height can look bigger than a body font at the same size, so you may need to adjust a step to compensate.

The point of a scale is that exceptions are rare and deliberate. If you find yourself adding a new size for every component, your ratio or number of steps probably needs rethinking.


FAQ: Modular Type Scales

There isn't a single best ratio. 1.25 suits most content sites, 1.2 or 1.125 suit interfaces and small screens, and 1.333 to 1.5 suit editorial and marketing pages with fewer heading levels. Test ratios with your actual font and content.

Usually your body text size, which is most often 16px or 1rem. Text-heavy sites may use 17px to 20px. Express the base in rem so the scale follows the reader's browser settings.

Often, yes. A smaller ratio on narrow screens keeps large headings from taking up too much space. A fluid scale built with clamp interpolates between the two ratios smoothly instead of switching at a breakpoint.

Most sites need five to eight: one or two below the base for small text, the base, and three to five above for headings. More steps usually mean the differences between them are too small to notice.

Yes. The pow() function is supported in current versions of all major browsers, so you can calculate each step from a ratio custom property. For older browser support, precompute the values instead.

No. Rounding to whole pixels or adjusting a step for a particular font is fine. The scale's job is to keep sizes consistent and related, so keep exceptions rare and deliberate.


Conclusion

A modular type scale gives you a small set of related font sizes by multiplying a base size by a fixed ratio. Choose your body text size as the base, pick a ratio that suits how many levels you need and how dramatic you want the hierarchy to be, and calculate the steps. Store them as custom properties, or let CSS calculate them with pow(), and map each element to a step.

For responsive sites, use a gentler ratio on small screens, ideally with a fluid clamp() scale that moves smoothly between the two, and always keep a rem component so text resizes properly. Once your scale is in place, font size decisions become quick and consistent, and the hierarchy across your whole site holds together.

Share :

Related Posts

How to make typography accessible?

How to make typography accessible?

You make typography accessible by choosing clear typefaces, setting text in relative units so it scales with user preferences, giving it enough colou

Dive Deeper
What are the parts of a letterform?

What are the parts of a letterform?

A letterform is the shape of a single letter, and typographers break it down into named parts. The main ones are the stem (the main vertical

Dive Deeper
What are ascenders, descenders and the baseline?

What are ascenders, descenders and the baseline?

The baseline is the invisible line that letters sit on. Ascenders are the parts of lowercase letters that rise above the x-height, such as th

Dive Deeper