Product DesignAugust 13, 20267 min read

Typographic scale for product UI: sizes that hold on every screen

Build a product type scale of 7 to 9 sizes, named as tokens, that holds from a 320px phone to a 1440px monitor and survives 200% zoom. The exact numbers and the floors.

letter wood stamp lot

A typographic scale is a fixed set of font sizes, each derived from a base size by a chosen ratio, that gives a product UI one hierarchy instead of dozens of one-off values. By the end of this guide you will have a scale of seven to nine sizes, named as tokens, that holds from a 320px phone to a 1440px monitor and passes a 200% zoom without the layout breaking.

The reason to do this is not aesthetic tidiness. It is that ad-hoc font sizes are the most common way a product UI starts to look unfinished. A button at 15px next to a label at 14px next to a heading at 22px reads as noise, because the eye cannot tell whether the differences are intentional. A scale makes every size a decision that was already made once, so the interface reads as one voice.

What you need before you start

  • A base body size. Use 16px. The web converged on it around 2010 because smaller text hurts readability at monitor distance, and iOS Safari zooms any form input under 16px.
  • A ratio. A single number between 1.2 and 1.333 that steps the sizes apart.
  • A place to store the values as tokens (CSS custom properties, a Figma variable collection, or both kept in sync).
  • Ten minutes and the willingness to hand-tune the small end, because pure ratio math breaks there.

Step 1: Set the base at 16px in rem

Declare the body size once, in rem, and let everything else reference it. rem is relative to the root font size, so it respects the user's browser setting and grows correctly under zoom. A size set in px ignores that setting entirely. This is the single largest accessibility difference in the whole scale: WCAG does not mandate a pixel minimum, but it does require that text resize to 200% without loss of content, and rem-based type does that for free.

Step 2: Pick a ratio, then stop trusting it at the small end

The ratio decides how much contrast sits between adjacent sizes. Minor Third (1.2) keeps steps close together and suits dense, data-heavy B2B tools. Major Third (1.25) is the safe default for most product UI. Perfect Fourth (1.333) opens a wider gap and reads as more editorial, better on marketing pages than inside an app. The Imperavi UI Typography reference is a good primer on why the ratio, not the individual sizes, is the thing you are actually choosing.

Here is the catch. Applied literally, a ratio collapses at the small end. From a 16px base, a 1.2 ratio gives you 13.3px, then 11.1px, then 9.3px for the two steps below body. Those are unreadable in a real interface. So you use the ratio to generate the large end and hand-tune the two sizes below body to legible values. This is not cheating. It is what every mature design system does.

Step 3: Name sizes as tokens, never as pixels

The scale below is a hand-tuned 1.25-ish major third with a base of 16px. Store each row as a semantic token, not as its pixel value, so a component references --font-size-body and never the literal number.

  • caption: 13px, line-height 18px
  • small: 14px, line-height 20px
  • body: 16px, line-height 24px
  • body-lg: 18px, line-height 28px
  • title-sm: 20px, line-height 28px
  • title: 24px, line-height 32px
  • heading: 30px, line-height 36px
  • display: 38px, line-height 44px

Eight sizes covers almost every product. If you find yourself reaching for a ninth, add it deliberately at the top for a hero, not in the middle to patch a spacing problem. Naming by role rather than by size is what lets you retune the whole scale later without touching a single component. Same idea as the rest of your token naming: the component asks for a job, the system answers with a value.

Step 4: Make only the display sizes fluid with clamp()

Fluid typography with clamp() scales a size smoothly between a minimum and a maximum as the viewport changes, with no breakpoints. The syntax is clamp(MIN, PREFERRED, MAX), and the preferred value must mix a viewport unit with a rem term, for example clamp(1.9rem, 1rem + 3vw, 2.75rem). The rem part is what keeps zoom working: a pure vw value locks to the viewport and refuses to grow when a user zooms in, which is a real accessibility failure documented at length by LogRocket and Smashing Magazine.

Apply fluid sizing only to heading and display. Body and UI text stay fixed. A fluid body size drifts as the window resizes, and on a narrow phone it can slip under the readable floor before you notice. Users read the body text. They glance at the display text. Fluidity belongs where the glance is, not where the reading is.

Step 5: Pair each size with a line height

Line height is part of the scale, not a separate decision. The rule that holds: larger text needs a tighter multiplier, smaller text needs a looser one. Body at 16px wants roughly 1.5 (24px). A 38px display wants about 1.15 (44px), because at that size a 1.5 multiplier opens a gap that breaks the headline into disconnected lines. Store line height alongside each size token so the pairing travels together and no one has to guess.

Step 6: Enforce the floors

Two hard floors sit under every product scale.

  • 14px is the practical minimum for any text a user has to read on desktop. 13px captions are fine for timestamps and metadata, not for content.
  • 16px is the minimum for form inputs on mobile. iOS Safari and Chrome auto-zoom into any input with a font size under 16px on focus, which yanks the viewport and disorients the user. The fix is one line: set inputs to 16px. Do not reach for maximum-scale=1 or user-scalable=no to suppress the zoom, because that disables pinch-zoom entirely and violates WCAG, as CSS-Tricks spelled out years ago.

How do you verify the scale holds?

Three checks, in order. First, zoom the browser to 200% and confirm the layout reflows without clipping or horizontal scroll. Second, open the same page at 320px width and confirm no body text has dropped below 14px and no input below 16px. Third, squint at a real screen: if you cannot instantly tell a heading from a subheading from body, the ratio is too tight and the steps need more contrast. The squint test catches what pixel measurements miss.

Common failures and fixes

  • The scale looks flat. Adjacent sizes are too close. Either raise the ratio or drop a middle step so the remaining sizes sit further apart.
  • Headings feel loose and disconnected. Line height is uniform across the scale. Tighten the multiplier on everything above title.
  • Mobile forms zoom on tap. An input is under 16px. Bump it. This is the single most common typographic bug in shipped SaaS.
  • Sizes drift back to arbitrary values over time. Components hard-code pixels instead of referencing tokens. Lint for raw font-size values that are not a token reference.

Where to take it next

The scale is one layer of a type system. Weight, letter spacing, and the relationship between font-size and spacing tokens all sit on top of it. If you are deciding where these values should live, the trade-offs are in our comparison of design tokens versus CSS variables versus Tailwind. The scale is the part that most directly decides whether a UI reads as finished, which is why it is worth getting exact before anything else.

Sources

Photo by Amador Loureiro on Unsplash

Frequently asked questions

What ratio should I use for a data-dense B2B app?

Use a Minor Third (1.2) with fewer steps. Dense interfaces pack many values close together, so a wide ratio wastes vertical space and forces too much jump between adjacent sizes. A 1.2 ratio keeps the increments small enough that a table cell, a label, and a section title can all sit near each other without shouting. Reserve the wider Perfect Fourth (1.333) for marketing pages, where contrast carries emotion rather than density.

Should the whole scale be fluid with clamp()?

No. Make only the two largest sizes fluid, the heading and the display. Body and UI text stay fixed at 16px. A fluid body size drifts as the window resizes, and on a narrow phone it can slip under the readable floor or below the 16px iOS input threshold before anyone notices. Users read body text and only glance at display text, so fluidity belongs where the glance is. Fixed body text is also easier to reason about when you debug spacing.

How many text sizes do I actually need?

Seven to nine covers almost every product. Fewer than six flattens the hierarchy and the interface reads as monotone. More than ten and people stop knowing which size to pick, so the scale drifts back to ad-hoc values, the exact problem it was meant to prevent. Eight is a good default: caption, small, body, body large, small title, title, heading, display. Add a ninth only at the top for a hero, never in the middle to patch spacing.

Does px versus rem really matter for accessibility?

Yes, and it is the cheapest accessibility win in the whole scale. A font size set in rem respects the user's root font size and grows under browser zoom, so someone who raises their default text size gets larger text everywhere. A size in px ignores that setting entirely. WCAG does not mandate a minimum pixel size, but it does require text to resize to 200% without losing content, and rem-based type satisfies that with no extra work. Set font-size in rem and reserve px for borders and hairlines.

Related articles

Studio

Start a project.

One partner for the whole build. Faster delivery, a modern stack, lower cost.