Modular Scale / The Rhythm of Type
Open a project without a design system and you will often find CSS like this: font-size: 13px, 14px, 15px, 17px, 18px, 22px, 24px, 26px… Each number had a reason at the time, but together they follow no pattern. The gaps between headings are uneven, h3 and h4 look almost the same, and a subheading in some corner is somehow bigger than an h2.
A modular scale is the most common fix: pick a base size and a ratio, and every font size becomes “base × ratio to the power n”. Instead of choosing sizes one by one, you take them from a single sequence.
This post starts with the principle, then looks at how different ratios feel, how they behave on phones and large screens, which ratios suit which kinds of sites, and finally walks through building a type scale you can actually use.
1. From metal type to a sequence: where the modular scale comes from
The traditional scale
In the days of metal type, a print shop could not cast a font in every possible size, so a fixed set of sizes developed. In The Elements of Typographic Style, Robert Bringhurst calls it the classical scale, in use since the sixteenth century:
6 7 8 9 10 11 12 14 16 18 21 24 36 48 60 72
It is not a strict geometric progression, but there is a clear pattern: small sizes are one point apart, and the gaps grow as the sizes grow. Large sizes need larger gaps for the eye to tell them apart. That is the core observation behind the modular scale.
In the same book, Bringhurst also uses musical intervals to discuss page proportions and type sizes, and shows how to interleave two sequences into one scale.
The modular scale on the web
In 2011 Tim Brown published “More Meaningful Typography” in A List Apart, bringing the idea to web design, and built the calculator modularscale.com with Scott Kellum. Since then you can see its influence in the type scale of almost every design system.
The formula is a single line:
size(n) = base × ratioⁿ
- base: usually the body text size, for example 16px. This is step 0.
- ratio: the multiplier between two adjacent steps, for example 1.25.
- n: the step. Positive steps are headings; negative steps are small text such as notes and captions.
With a 16px base and a ratio of 1.25:
| Step | Calculation | Size | Possible use |
|---|---|---|---|
| −1 | 16 ÷ 1.25 | 12.8px | Captions, notes |
| 0 | 16 | 16px | Body text |
| 1 | 16 × 1.25 | 20px | h4 |
| 2 | 16 × 1.25² | 25px | h3 |
| 3 | 16 × 1.25³ | 31.25px | h2 |
| 4 | 16 × 1.25⁴ | 39.06px | h1 |
| 5 | 16 × 1.25⁵ | 48.83px | Home page headline |
2. Why geometric and not arithmetic
If each step simply added 4px (16, 20, 24, 28, 32), that would look tidy too. Why multiply?
The eye perceives ratios, not differences
Psychophysics has a basic observation: how strongly we perceive a stimulus depends roughly on the relative change, not the absolute difference. The nineteenth-century Weber–Fechner law describes exactly this; S. S. Stevens’ power law later refined the details, but the direction — perception is roughly proportional — stayed the same.
Applied to type sizes:
- Going from 12px to 16px (+4px) is a very obvious change: a third larger.
- Going from 60px to 64px (+4px) is barely visible: less than 7% larger.
An arithmetic sequence jumps too hard at small sizes and hardly changes at large ones. A geometric sequence makes every step look equally far apart, so the hierarchy feels even. That is the most solid reason for a modular scale, more important than any aesthetic claim.
xychart-beta
title "16px base: size at each step for different ratios (px)"
x-axis "Step" [-1, 0, 1, 2, 3, 4, 5]
y-axis "Size (px)" 0 --> 120
line [14.2, 16, 18, 20.2, 22.8, 25.6, 28.8]
line [13.3, 16, 19.2, 23, 27.6, 33.2, 39.8]
line [12, 16, 21.3, 28.4, 37.9, 50.6, 67.4]
line [9.9, 16, 25.9, 41.9, 67.8, 109.7, 177.4]
The four lines, bottom to top: major second 1.125, minor third 1.2, perfect fourth 1.333, golden ratio 1.618. The larger the ratio, the sooner the curve shoots up; the golden ratio leaves the chart at step 5.
A few sizes that relate to each other
The second benefit of a modular scale is constraint. The sequence offers a limited set of sizes, so you can only choose from it, and “almost the same” numbers like 15px and 17px never appear. Because every size comes from the same ratio, they share a consistent relationship, and the layout gains an order you may not be able to name but can feel.
3. Common ratios and their character
Modular scale ratios usually borrow the names of musical intervals. A note and the note an octave above have a frequency ratio of 2:1; a perfect fifth is 3:2, a perfect fourth 4:3, and so on.
To be clear up front: there is no proven link between musical harmony and visual harmony. The interval names are a convention that makes ratios easier to remember and talk about; a “perfect fifth” scale is not more harmonious because of its name. What actually affects perception is how big the ratio is.
Steps −1 to 6 for each ratio, with a 16px base:
| Name | Ratio | −1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|---|---|
| Minor second | 16:15 (1.067) | 15.0 | 16 | 17.1 | 18.2 | 19.4 | 20.7 | 22.1 | 23.6 |
| Major second | 9:8 (1.125) | 14.2 | 16 | 18.0 | 20.2 | 22.8 | 25.6 | 28.8 | 32.4 |
| Minor third | 6:5 (1.2) | 13.3 | 16 | 19.2 | 23.0 | 27.6 | 33.2 | 39.8 | 47.8 |
| Major third | 5:4 (1.25) | 12.8 | 16 | 20.0 | 25.0 | 31.2 | 39.1 | 48.8 | 61.0 |
| Perfect fourth | 4:3 (1.333) | 12.0 | 16 | 21.3 | 28.4 | 37.9 | 50.6 | 67.4 | 89.9 |
| Augmented fourth | 1:√2 (1.414) | 11.3 | 16 | 22.6 | 32.0 | 45.3 | 64.0 | 90.5 | 128.0 |
| Perfect fifth | 2:3 (1.5) | 10.7 | 16 | 24.0 | 36.0 | 54.0 | 81.0 | 121.5 | 182.2 |
| Golden ratio | 1:1.618 | 9.9 | 16 | 25.9 | 41.9 | 67.8 | 109.7 | 177.4 | 287.1 |
Small ratios: 1.067–1.125 (minor and major second)
Feel: steady, dense, restrained. Adjacent steps differ only a little, and sometimes need help from weight or colour to be told apart.
- Pros: many levels without losing control; suits dense interfaces where headings shouldn’t steal the show.
- Cons: weak hierarchy, so readers scanning the page can’t find the key points; if size is the only signal, h4 and h5 look almost identical.
- Good for: admin panels, dashboards, documentation sites, form-heavy applications, interface text on phones.
Medium ratios: 1.2–1.25 (minor and major third)
Feel: balanced, clear, dependable. Headings are clearly larger than body text without overpowering the content.
- Pros: the most versatile choice; four or five heading levels stay distinct, and the largest size still fits on a phone screen.
- Cons: not much personality — the “safe” option; on large screens, big headlines may lack presence.
- Good for: blogs, news, knowledge content, most corporate sites. Many design systems default to this range.
Larger ratios: 1.333–1.414 (perfect and augmented fourth)
Feel: editorial and dramatic, with strong contrast between headings and body text, like a magazine.
- Pros: hierarchy is obvious at a glance and headlines have presence; the layout itself can carry the style.
- Cons: fewer usable steps — step 6 is already 90–128px; small sizes drop quickly, and step −1 may be too small (only 11–12px on a 16px base).
- Good for: portfolios, magazine-style articles, brand sites, reading-focused blogs that want character.
The augmented fourth (√2) matches the aspect ratio of A-series paper, which gives some consistency when the design goes alongside print or A4 layouts.
Large ratios: 1.5–1.618 (perfect fifth, golden ratio)
Feel: poster-like, bold, declarative. A handful of sizes already create enormous contrast.
- Pros: maximum visual impact; suits layouts with only two levels, headline and body.
- Cons: very few usable steps; step 4 is already over 80–110px, impossible on a phone; intermediate levels (h3, h4) are hard to place.
- Good for: hero sections, event pages, slides, posters. Usually used in one section, not across a whole site.
About the golden ratio
The golden ratio is often called “the most beautiful proportion”, but the evidence is weaker than that. Fechner ran rectangle-preference experiments in 1876 and concluded that people prefer rectangles close to the golden ratio; later studies have been inconsistent, and preferences turn out to depend on the experimental method, how the options are arranged, and culture. Applied to type, its practical effect is simply “a very large ratio”, indistinguishable from 1.6.
Choosing the golden ratio is fine, but the reason should be “I need very strong contrast”, not “it’s inherently more beautiful”.
4. The visual psychology behind the ratio
A type scale’s ratio affects more than size; it shapes the reader’s overall impression of the content. These are common observations from design practice, useful as a reference when choosing:
| Aspect | Small ratio (≤ 1.2) | Large ratio (≥ 1.333) |
|---|---|---|
| First impression | Calm, professional, tool-like | Confident, opinionated, dramatic |
| Reading rhythm | Continuous and even; suits long reading and operation | Clear pauses and sections, like leafing through a magazine |
| Scanning | Needs weight, colour and white space to help | Size alone reveals the structure |
| Information density | More content per screen | Headings take a lot of space and push content down |
| Brand voice | Plain, trustworthy, understated | Vivid, avant-garde, emotional |
A few things worth noticing:
- Contrast gets diluted by overuse. A large ratio is powerful on one big headline; give every section a huge heading and nothing stands out.
- Hierarchy comes from several dimensions. Size, weight, colour, case, letter spacing and white space all separate levels; the smaller the ratio, the more the others have to help.
- Large type needs tightening. The bigger the text, the looser its letter spacing and line height look. Headlines usually want a line height of 1.1–1.3, and Latin text can take slightly tighter tracking; body text stays at 1.5 or more (1.7–2 for Chinese).
- Small text is the hidden cost of a ratio. The larger the ratio, the faster sizes fall below the base; captions, notes and tables easily become too small to read. Handle small sizes separately, or fill the gaps with half steps (see section 7).
5. Devices and screen sizes
The same ratio behaves completely differently on screens of different sizes.
Why phones need a smaller ratio
Say the desktop uses a perfect fourth on a 16px base, so h1 is step 4 at 50.6px. On a 375px-wide phone, after side margins you have about 340px — six or seven Chinese characters per line, or a handful of English words. Headlines break into three or four lines and fill the entire first screen.
Conversely, if the whole site uses a major second, h1 is only 25.6px on a 27-inch monitor: the headline looks timid and the layout feels empty, with no focal point.
So the common approach is: a smaller ratio on phones, a larger ratio on wide screens, and a smooth transition in between.
| Device | Typical reading distance | Suggested ratio | Notes |
|---|---|---|---|
| Phone | 30–40 cm | 1.125–1.2 | Narrow screen fills up fast with large type; phones are held close, so body text needn’t be big |
| Tablet | 35–45 cm | 1.2–1.25 | In between |
| Laptop, desktop | 50–70 cm | 1.25–1.333 | Enough room for clear hierarchy |
| Large screen, TV | 1 m or more | 1.333 or more, with larger body text | Viewed from afar, so every size grows and the hierarchy should be stronger |
Don’t forget the body size
Choose the base size according to reading distance and typeface:
- Latin-script sites commonly use 16–20px body text. Typefaces with a large x-height (the height of lowercase letters) look bigger and can go a little smaller.
- Chinese characters are square and fill the whole em box, so 16px Chinese looks larger and denser than 16px Latin text. Chinese body text commonly sits at 15–18px with a line height of 1.7–2.
- Chinese has no case and no x-height, so its hierarchy relies more on size and weight; but large Chinese headlines on a big ratio look very “heavy” because of stroke density, which a lighter weight can balance.
Fluid type: smooth transitions with clamp()
The old approach switched sizes at a few media-query breakpoints, so text jumped whenever the window crossed one. Today CSS clamp() lets the size change continuously with the viewport width:
font-size: clamp(minimum, value that changes with width, maximum);
The middle value is a straight line: at the minimum width it equals the phone size, at the maximum width the wide-screen size. To calculate it:
slope = (wide size − phone size) ÷ (max width − min width)
intercept = phone size − slope × min width
value = intercept + slope × 100vw
Take this site’s h2: on phones it is minor-third step 2 (18.72px), on wide screens perfect-fourth step 2 (23.11px), transitioning between 320px and 914px:
slope = (23.11 − 18.72) ÷ (914 − 320) = 0.00739 → 0.739vw
intercept = 18.72 − 0.00739 × 320 = 16.35px → 1.022rem
--fs-h2: clamp(1.17rem, 1.022rem + 0.739vw, 1.444rem); /* 18.7 → 23.1px */
If you’d rather not do the maths by hand, Utopia is a tool built for fluid type: enter the base size and ratio for phones and wide screens, and it outputs the whole set of clamp() values.
Accessibility: never use vw alone
When a font size is written only in vw, zooming in the browser (Ctrl +) doesn’t make the text bigger, because vw depends only on the viewport width. That fails WCAG 1.4.4, which requires text to be resizable up to 200%.
- Always mix
remandvwin the middle value (like1.022rem + 0.739vwabove), so therempart grows when the user zooms. - Write the minimum and maximum in
rem, respecting the default font size the user set in the browser. - Keep the gap between minimum and maximum modest. The bigger the gap, the more weight the
vwpart carries and the weaker zooming becomes; a common guideline is to keep the maximum no more than 2.5 times the minimum.
6. Choosing for different kinds of sites
| Site type | Suggested ratio | Hierarchy needs | Notes |
|---|---|---|---|
| Admin, dashboard, SaaS UI | 1.067–1.2 | Many levels, none large | Interface text mostly sits between 12 and 20px; hierarchy comes from weight and colour |
| Technical documentation | 1.125–1.2 | h1–h4 all in use | Deep hierarchy; a big ratio makes h1 huge; align code size with body text |
| Blog, knowledge content | 1.2–1.333 | Mostly h1–h3 | Comfortable body text matters most; headings just need to be clear |
| News, magazine | 1.25–1.414 | Headline, deck, body | The front page needs many headline sizes to rank stories |
| E-commerce | 1.2–1.25 | Product name, price, description | Price and buttons often matter more than headings |
| Brand, portfolio | 1.333–1.618 | Few levels, strong contrast | The headline is part of the design; watch that small text doesn’t get too small |
| Event page, hero | 1.5 or more | Headline and body | Can use a separate scale for just that section |
7. Building a type scale step by step
flowchart TD
A["1. Choose the body size<br>(the base, step 0)"] --> B["2. List the roles you need<br>body, h1–h4, captions, buttons…"]
B --> C["3. Pick the wide-screen ratio<br>by site type and number of levels"]
C --> D["4. Compute the sequence<br>and map roles to steps"]
D --> E{"Does the largest size<br>fit on a phone?"}
E -- No --> F["5. Use a smaller ratio on phones<br>and transition with clamp()"]
E -- Yes --> G
F --> G["6. Test with real content<br>in each language and device"]
G --> H{"Are two steps too close<br>or too far apart?"}
H -- Yes --> I["Add half steps<br>or remap roles"]
I --> G
H -- No --> J["7. Turn it into design tokens<br>CSS uses role variables only"]
The process of building a type scale. Expect a few rounds of adjustment after step 6.
Step 1: Choose the base size
Start with the body text, because it is what readers look at the longest. Test with the real typeface and real content: read a few paragraphs on the target devices at a normal distance, then decide. Don’t work backwards from the headings.
Step 2: List the roles
List the text roles the site actually uses, without assuming you need h1 through h6:
- Body text, quotes, tables
- Post title, h2, h3, h4
- Captions, meta (date, author), footer
- Navigation, buttons, tags
- Special large type: home page headline, 404 page, data callouts
The number of roles decides how many steps you need. The more steps, the smaller the ratio has to be, or the largest size gets out of hand.
Step 3: Pick the ratio
Choose the wide-screen ratio from the table in section 6, then check which step the largest role lands on and how many pixels that is. If it’s reasonable, carry on; if it’s too big, pick a smaller ratio or put the largest role on a lower step.
Step 4: Map roles to steps
You don’t need to use every step, and the steps you use don’t have to be consecutive. For example:
| Role | Step | Major third (1.25), 16px |
|---|---|---|
| Caption, meta | −1 | 12.8px |
| Body text | 0 | 16px |
| h4 | 1 | 20px |
| h3 | 2 | 25px |
| h2 | 3 | 31.25px |
| Post title | 4 | 39.06px |
| Home page headline | 6 | 61.04px |
The home page headline deliberately skips step 5 to put distance between it and the post titles, making it the one visual focal point.
Step 5: The phone ratio and fluid type
Use a smaller ratio on phones (usually one or two notches below wide screens, for example 1.333 on wide screens and 1.2 on phones), compute both ends with the same steps, and connect them with clamp().
CSS now has pow(), so you can compute the scale in CSS itself; all major browsers have supported it since late 2023:
:root {
--base: 1rem;
--ratio: 1.25;
--fs-h3: calc(var(--base) * pow(var(--ratio), 2));
--fs-h2: calc(var(--base) * pow(var(--ratio), 3));
}
/* Use a smaller ratio on phones; every size follows */
@media (max-width: 40rem) {
:root { --ratio: 1.2; }
}
This is easy to read and adjust, but it can only switch at breakpoints. For a smooth transition you still need precomputed clamp() values. A practical approach is a small script that generates the CSS:
// Generate fluid type: separate ratios for phones and wide screens,
// interpolated linearly between minVw and maxVw
function fluidScale({ base = 16, minRatio, maxRatio, minVw = 320, maxVw = 1280, steps }) {
const rem = (px) => +(px / 16).toFixed(4);
return Object.entries(steps).map(([name, step]) => {
const min = base * minRatio ** step;
const max = base * maxRatio ** step;
const slope = (max - min) / (maxVw - minVw);
const intercept = min - slope * minVw;
// For negative steps the phone size is actually larger, so swap clamp's bounds
const [lo, hi] = min < max ? [min, max] : [max, min];
const vw = +(slope * 100).toFixed(4);
const value = `${rem(intercept)}rem ${vw < 0 ? '-' : '+'} ${Math.abs(vw)}vw`;
return `--fs-${name}: clamp(${rem(lo)}rem, ${value}, ${rem(hi)}rem);`;
}).join('\n');
}
console.log(fluidScale({
minRatio: 1.2,
maxRatio: 1.333,
steps: { small: -1, h4: 1, h3: 2, h2: 3, h1: 4 },
}));
Note the detail in the comment: with a smaller phone ratio, negative steps (text smaller than the body) end up larger on phones than on wide screens. 16 ÷ 1.2 = 13.3px, while 16 ÷ 1.333 = 12px. That’s actually a good thing: slightly larger small text is easier to read on a phone.
Step 6: Test with real content
The maths only gives you a starting point; your eyes make the final call:
- Use real content: the longest and shortest titles, mixed Chinese and English, numbers, code.
- Check the narrowest screen: at 320px, how many lines does the biggest headline wrap to? Does a single word or a lone Chinese character drop onto the last line?
- Compare adjacent steps side by side: can you tell h3 from h4? Captions from body text?
- How to adjust: if two steps are too close, move a role up a step; if they’re too far apart, add a half step.
A half step (part of a double-stranded scale) inserts an extra step between two steps, which amounts to using the square root of the ratio as the interval. For a perfect fourth, the half-step interval is √1.333 ≈ 1.155. This gives even a large-ratio scale fine-grained intermediate sizes without replacing the whole ratio.
Tim Brown’s original approach uses two bases (say, 16px body text and another important measure, such as a fraction of the layout width), generates a sequence from each and interleaves them. The effect is similar: denser steps, still sharing a consistent proportion.
Step 7: Turn it into design tokens
Finally, write the sizes as variables named after roles, and have the CSS use only the roles, never raw numbers:
:root {
--fs-meta: …; /* captions, dates */
--fs-body: …; /* body text */
--fs-h3: …;
--fs-h2: …;
--fs-title: …; /* post title */
}
h2 { font-size: var(--fs-h2); }
.post-meta { font-size: var(--fs-meta); }
Changing the ratio then means editing the variables (or even just --ratio) instead of hunting for numbers. Naming by step (--step-2) works too, but role names are harder to misuse: nobody will switch the meta text to --step-1 just to “make it a bit bigger”.
8. Line height and vertical rhythm
Once the type scale is in place, line height and spacing have to follow, or the layout will still look messy however tidy the sizes are.
- The body line height is the spacing unit. With 16px body text and a line height of 1.75, one line is 28px; the space above and below paragraphs and headings uses multiples (or halves) of 28px.
- Heading line heights are calculated from their size and rounded to a multiple of the base line (or half line), so the body text after them lands back on the same baseline grid. CSS
round()does this directly:
h2 {
font-size: var(--fs-h2);
line-height: 1.3; /* for browsers without round() */
line-height: round(up, 1em * 1.3, var(--half-line)); /* round up to a multiple of half a line */
}
- Larger type takes a smaller line-height ratio: 1.6–2 for body text, 1.1–1.3 for headings, down to 1.0 for display type.
- Spacing can be generated from the same ratio (
--space-1,--space-2…), so type and white space come from one system.
9. Case study: the type on this site
This site (blog.starcklin.com) uses a perfect fourth. Here are the actual settings and the reasons behind them:
- The body text base is 13px. That is the style of the original WordPress theme: light weight, double line height, lots of white space. Within the type scale the body size doesn’t change with the screen width; only the headings are fluid.
- The whole thing is then enlarged via the root font size: every measurement is in rem, so phones and tablets set the root to 120% (15.6px body text), and the two-column layout (1056px and up) grows with the viewport to 150% (1440px and up, 19.5px body text). The proportions between sizes stay the same; the whole set just scales up, and text never shrinks back at a breakpoint as the window widens. The table below shows the values before enlarging.
- Each step is × 1.333 on wide screens and × 1.2 on phones, transitioning with
clamp()between 320px and 914px. 914px is where the original site switched to two columns. - Half steps fill the gaps: perfect-fourth step −1 is only 9.75px, too small, so meta text uses step −0.5 (11.9px on phones, 11.3px on wide screens); step 1 is too far from the body, so h4 uses step 0.5.
- Headings use whole steps only, mapped to HTML levels: h1 is step 3, h2 step 2, h3 step 1. Post titles in the home page list are h2, so they use step 2; the title on the post page is h1 and uses step 3, a full step larger. The post page title used to be step 3.5, only half a step (1.155×) above the list, which looked less like a deliberate distinction than an inconsistency, so it moved to whole steps.
- Large type is deliberately set apart: the site name is step 4 (up to 41px), a full step above post titles; the big 404 text is step 6 (up to 73px), skipping step 5.
- Line heights round to half lines: heading line heights use
round()to a multiple of 13px (half a line), so the body text after them returns to the 26px baseline grid.
| Role | Step | Phone → wide screen |
|---|---|---|
| Meta, captions | −0.5 | 11.9 → 11.3px |
| Body text | 0 | 13px |
| h4 | 0.5 | 14.2 → 15.0px |
| h3 | 1 | 15.6 → 17.3px |
| Body h2 | 2 | 18.7 → 23.1px |
| Post titles in lists (h2) | 2 | 18.7 → 23.1px |
| Post page title (h1) | 3 | 22.5 → 30.8px |
| Site name | 4 | 27.0 → 41.1px |
| 404 big text | 6 | 38.8 → 73.0px |
During development I also built minor-third, major-third and major-second variants, generated by the same Python script and switchable with a URL parameter for comparison. After looking at them side by side, I chose the perfect fourth for having the strongest contrast and the most editorial feel.
10. Common mistakes
- Using every step: the sequence is a list of candidates. Choosing by role and skipping steps is what creates rhythm.
- Sacrificing readability for the maths: computing 12.8px doesn’t mean you must use 12.8px. Readability always comes first; add a half step or set a size by hand when needed.
- Using one ratio for desktop and phone: a large ratio will break on phones; a small one looks flat on large screens.
- Fluid type in vw alone: users can no longer enlarge the text, which fails accessibility requirements.
- Expecting the ratio to solve all hierarchy problems: weight, colour and white space matter as much as size. The smaller the ratio, the more you need them.
- Believing in a “magic ratio”: neither the golden ratio nor interval names have special powers. Choose a ratio based on how many levels the content needs, how much contrast you want, and whether it fits on the smallest screen.
- Ignoring the typeface: at the same size, typefaces differ a lot in x-height and width. When you change typefaces, the base size may need to change too.
Conclusion
The value of a modular scale isn’t the maths itself; it’s that it turns choosing font sizes from a string of scattered decisions into one reasoned decision: pick a base and a ratio, and everything else follows from a single sequence.
When choosing a ratio, ask yourself three questions:
- How many levels does this site need?
- Should it feel quiet, or strong?
- Does the largest size fit on a 320px-wide phone?
Answer them and you have a starting point. Then test and tweak with real content on real devices: let the maths take care of consistency, and let your eyes make the final call.