Skip to content

Readable Type

The smallest size a thing is allowed to be, and what may change when the screen does. Sixteen for body and inputs, fifteen for navigation, twelve as the floor — at every width.

Also called Minimum Font Size, Legibility — in this system all of them are Readable Type.

Live preview

Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.

Same panel, two size decisions
The standard sizes15 / 16 / 12. The same on a phone.
OverviewDeploymentsMonitoring

The last build reached every production region. Nothing is waiting on approval.

Updated 4 minutes ago · 3 regions

Shrunk “for mobile”12 / 13 / 10. Two lines saved, one reader lost.
OverviewDeploymentsMonitoring

The last build reached every production region. Nothing is waiting on approval.

Updated 4 minutes ago · 3 regions

The floor

Four sizes carry a product. Twelve is the smallest, it is for metadata, and there is nothing under it — the last two rows are on this table so they can be pointed at, not chosen.

SpecimenSizeWhat it may carry
Renews on the 26th16px
Body copy and every inputallowed

Prose, and any field typed into or read back. Below 16px, iOS zooms the page on focus.

Renews on the 26th15px
Navigation and UI textallowed

Sidebar rows, menus, list rows, tabs. Scanned from the corner of the eye, so it cannot be small.

Renews on the 26th13px
Supporting textallowed

Help text and table cells — text explaining something already on the screen.

Renews on the 26th12px
Captions and metadataallowed

Timestamps, counts, hints. The floor: correct here, and never used for anything a user must read to proceed.

Renews on the 26th11px
Nothingnever

The size a legend, an axis label or a "beta" pill gets talked into. It is information, set below the size it can be read at.

Renews on the 26th10px
Nothingnever

Illegible to a large share of adult readers at arm’s length. If it fits only at 10px, the layout is the problem.

Desktop, tablet, phone

One row of this table never changes. Everything else is where a responsive design is supposed to do its work — and the reason it never has to touch the reading sizes.

WhatDesktopTabletPhone
Body, UI, supporting, caption sizesnever changes
16 / 15 / 13 / 12SameSame
Display and h1–h2
44 / 32 / 2436 / 28 / 2228 / 24 / 20
Measure
68ch62chWhatever fits
Columns
Two or threeTwoOne
Row height / touch target
32px rows40px44px minimum
How much is on screen
All of itMostThe important part

The reader’s own setting

The container below stands in for the root element, which is what a browser’s text-size setting actually moves. One column follows it; the other cannot.

Reader’s text size
Sized in remFollows the reader

Your subscription renews on the 26th.

Sized in pxIgnores the reader

Your subscription renews on the 26th.

Every reader who has raised this setting did it because the default was too small for them. A px font size is a decision to overrule that, silently, on every screen — and it is invisible to anyone testing at 100%.

Interaction states

Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.

Renews on the 26th
Body16px — prose and inputs
Deployments
UI15px — text you click
Applies to this workspace
Supporting13px — explains what is there
4 minutes ago
Caption12px — the floor
4 minutes ago
Below the floor11px — not allowed
Deployments
PhoneIdentical to desktop

Anatomy

Every part, every measurement, and the reason it is that number.

Deployments

The last build reached every production region, and nothing is waiting on approval.

Updated 4 minutes ago

Three sizes, three jobs, and a row tall enough to hold the first one. Nothing here changes on a phone except the width of the paragraph.

  1. Navigation text15px / 21px / 470

    Read from the corner of the eye by someone deciding where to go next, not by someone already looking at it. One step under body, and never under 15px.

  2. Row height32px

    The line box is 21px; the remaining 11px is what stops a list of destinations reading as a wall. On touch the target grows to 44px without the text changing.

  3. Body16px / 1.6

    The default for prose and for every input. Below 16px an input also makes iOS Safari zoom the page on focus, which leaves the reader scrolled sideways.

  4. Measure46ch

    In ch, so it is still a measure after a size change or a zoom. This is the value that narrows on a phone — the size is not.

  5. Metadata12px

    The floor, and the only size allowed to sit on it. If a caption has to go smaller to fit, the caption is too long or the card is too small.

Design tokens used

Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.

Color

TokenValueUsed for
--ds-fg-secondary—Body at 4.5:1 or better
--ds-fg-muted—Metadata — verified at 12px, never below

Spacing

TokenValueUsed for
row heightHolding a 15px label comfortably

Typography

TokenValueUsed for
--text-bodyBody copy and every input
--text-uiNavigation, menus, list rows
--text-body-smSupporting copy
--text-captionMetadata — the floor
--text-displayThe one place responsive sizing belongs

Recommended sizes

Pick a size from this table. Do not invent a new one — a fourth height is how a design system starts dying.

SizeHeightTypeMax widthTouch targetWhen to use
Body16px400 weight68chn/aProse and every input. The size the product is read at.
UI15px470 weight40ch44px row on coarse pointersNavigation, menus, list rows, tabs.
Supporting13px400 weight72ch—Help text and table cells. Never the only copy explaining a control.
Caption12px400 weight60ch—Timestamps, counts, hints. The floor.
Below 12px————Not a size. If something only fits at 11px, cut the string or widen the container.

Do

1440px · 15px390px · 15pxunchanged
Hold every reading size across every breakpointA phone is further from the eye, in worse light, more often in motion. Shrinking type there inverts the need. Narrow the measure, drop a column, show less — the sizes do not move.
Set inputs at 16pxTwo reasons that happen to agree: a value being typed or checked is read carefully, and any font size under 16px makes iOS Safari zoom the page when the field takes focus — which leaves the reader scrolled sideways in a form they were halfway through.
Resize the window
Reserve clamp() for display typeA 44px hero genuinely cannot stay 44px at 390px — that is four words a line. Everything at h3 and below already fits at every width, so scaling it can only make it smaller than it was designed to be.
32px row24px row
Give the size somewhere to sitRaising a label from 12px to 15px inside a 24px row buys nothing: the line box is 21px and the row now reads as packed. Readability is the size and the space around it, and the second half is the one that gets forgotten.

Don't

@media (max-width: 640px) { body { font-size: 0.8125rem; } }@media (max-width: 640px) { .col { max-inline-size: 100%; } }
Do not shrink text at a mobile breakpointIt is the most common readability defect in shipped products, and it is invisible in review because reviews happen on desktops. The line that does it is usually one media query written to stop a heading wrapping.
* excludes taxFree tier onlyboth illegible
Do not put required information below 12pxA chart legend, a required-field marker, a price qualifier. If the reader needs it to act correctly, it is not metadata — and 11px is the size at which a meaningful share of readers stop being able to resolve it at arm’s length.
font-size: 15px;font-size: 0.9375rem;
Do not size text in pxIt overrides the reader’s own browser setting — the one they changed because the default was too small for them. rem answers that setting; px silently discards it, and nobody testing at 100% will ever see the difference.

Every row in this table was set two steps down so one more row would fit above the fold, which is a trade nobody described out loud.

Do not buy density out of the type sizeDensity is bought with layout — fewer columns, tighter rows, less on screen. Buying it from the font size charges the whole cost to whoever has the weakest eyes, and saves about two lines.

Accessibility

Not a checklist to run at the end. These are the requirements the component was built from.

1.4.4Resize TextAA1.4.10ReflowAA1.4.12Text SpacingAA1.4.3Contrast (Minimum)AA1.4.8Visual PresentationAAA

Contrast

  • Small text needs more contrast, not less. 12px metadata on --ds-fg-muted is verified at 4.6:1 on --ds-surface; the same colour one step smaller has no verification and no excuse.
  • The "large text" exception (18.66px regular, 14px bold, 3:1) exists for headings. Designing ordinary UI text down to it is using an exemption as a target.
  • Never let a lighter weight stand in for a smaller size. 400 and 600 of one colour measure identically, so weight cannot rescue text that is too small to resolve.

Keyboard

⌘ / Ctrl +Browser zoom to 200%. The page must reflow without horizontal scrolling at 320px CSS width.
Browser text sizeIndependent of zoom, and the setting rem answers. Set it to 20px and reload before shipping.

Screen readers

  • Text size is invisible to a screen reader and critical to everyone using magnification, which is a much larger group. Do not treat "it is announced correctly" as covering it.
  • Never render text as an image to keep it small and sharp. It cannot be resized, selected, translated or read aloud.
  • A 200% zoom is the assistive technology most people actually use, and it is built into every browser they already have.

Focus & touch

  • Nothing here is focusable on its own, but everything here is what a focused control says. A focus ring around an 11px label is a ring around something the reader still cannot read.
  • On a coarse pointer every row is at least 44px, and the text inside it does not change size to pay for that. A 15px label in a 44px row is the correct phone treatment; a 12px label in a 44px row is a big target for something you cannot read.
AttributeApplied toNotes
font-sizeAny text nodeIn rem. This is an accessibility property before it is a visual one.
line-heightAny text blockUnitless, and at least 1.5 for body copy — WCAG 1.4.12 requires that it survive being overridden.
meta[name=viewport]The documentNever user-scalable=no, and never maximum-scale=1. Both disable pinch zoom, which is the only tool some readers have.

Code

Example usage

tsx
1// Text you click is text-ui, in a row tall enough to hold it.2<a className="flex h-8 items-center rounded-md px-2.5 text-ui">Deployments</a>34// Text you read, and text you type into, are both text-body.5<p className="max-w-[68ch] text-body text-fg-secondary">{description}</p>6<input className="h-9 w-full rounded-md px-3 text-body" />78// Metadata is text-caption, and there is nothing below it.9<span className="text-caption text-fg-muted">{relativeTime(updatedAt)}</span>1011// A phone gets the same sizes. What it gets less of is content.12<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">{cards}</div>

CSS

The whole standard, as CSS. Four sizes, four units, one media query that does not touch them.

css
:root {
  /* rem, so the reader's own browser text-size setting still applies. */
  --text-body: 1rem;        /* 16 — prose and every input   */
  --text-ui: 0.9375rem;     /* 15 — navigation and UI text  */
  --text-body-sm: 0.8125rem;/* 13 — supporting copy         */
  --text-caption: 0.75rem;  /* 12 — metadata. The floor.    */
}

/* Line heights are unitless: they multiply against whatever size the
   element ends up at, so they survive inheritance and user zoom. */
body { font-size: var(--text-body); line-height: 1.6; }
.nav-item { font-size: var(--text-ui); line-height: 1.4; font-weight: 470; }

/* em only for what is genuinely relative to the current font. */
h2 { letter-spacing: -0.017em; }

/* ch for reading width, because a measure is counted in characters. */
.prose { max-inline-size: 68ch; }

/* Inputs sit at body size. Under 16px, iOS zooms the page on focus. */
input, textarea, select { font-size: var(--text-body); }

/* clamp() belongs to display type and nothing else. */
.hero { font-size: clamp(1.75rem, 1.25rem + 2.2vw, 2.75rem); line-height: 1.1; }

/* The only thing a breakpoint changes about text is how wide it runs. */
@media (max-width: 640px) {
  .prose { max-inline-size: 100%; }
  /* No font-size here. Not one. */
}

Notes

Professional tips

  • The quickest audit in the codebase: grep for font-size values under 0.75rem, and for any font-size inside a max-width media query. Those two searches find almost every violation of this page.
  • When a string only fits at 11px, the string is the problem. Shorten the label, widen the column, or wrap — three fixes that cost nothing, against one that costs a reader.
  • Raising a size usually means raising the box around it too. A 15px label needs a 32px row; dropping it into a 24px row trades one readability problem for another.
  • Test with the browser text size at 20px, not just at 200% zoom. They are different settings, and only one of them exposes px font sizes.

Performance

  • Larger type costs nothing to render. It costs layout — fewer rows above the fold — which is a design decision, not a performance one.
  • Holding sizes constant across breakpoints removes a whole class of media queries, and with them the CSS that goes stale the next time the scale moves.
  • clamp() is resolved by the browser at layout time with no JavaScript and no resize listener. Where it belongs it is free; the argument against it on body text is never performance.

Common mistakes

  • Shrinking type at a mobile breakpoint to keep a desktop layout intact. The layout was the thing that was supposed to change.
  • Setting font-size in px, which overrides the reader’s browser setting — the most common accessibility defect in shipped design systems.
  • Treating 12px as a general small size rather than as the floor for metadata. It is the last step, not a spare one.
  • Using user-scalable=no in the viewport meta to stop iOS zooming a 14px input. Fix the input size; disabling pinch zoom takes the workaround away from the reader too.

Real-world recommendations

  • Hold the phone at the distance you actually hold a phone — not the distance you hold it when checking your work — and read the smallest string on the screen. That is the test.
  • Ask someone over fifty to read the densest screen in the product. It takes two minutes and it will find things no contrast checker reports.
  • Sizes chosen on a 27-inch display at 100% scaling are chosen roughly 30% larger than they will be seen on a laptop at default scaling. Check on the smaller machine before deciding something can go down a step.
  • When a stakeholder asks for more on the screen, offer fewer columns or a denser layout first. "We will shrink the text" is the answer that sounds cheapest and is not.