Typography

Root does not invent one scale for every platform. iOS aliases the system text styles so Dynamic Type still works. Web uses three Inter sizes. Personality comes from family, a narrow weight band, and color.

Why alias, not invent

On iOS the type scale is not only visual. The system styles (.body, .largeTitle, and the rest) are what connect to Dynamic Type. Dynamic Type is what lets a person with low vision use the app.

A custom scale next to the system scale means every size must be rescaled by hand. Someone will forget. Some text will not grow. That is not a foundation. So the scale is the system scale, and the four decisions below carry the character.

Web has no Dynamic Type contract. It does not copy the iOS ladder. Web uses three sizes. Rank on the web comes from color, then weight.

Two layers

Atomic tokens exist so a value is not written twice. Composite styles are what you use for almost all text. If size and line height are set apart, they drift. A composite style cannot drift.

  1. Atomic tokens. Family, size in points, line height in points, and weight. Use these for a rare case, such as an icon that must match a size.
  2. Composite styles. body, headline, display, and the rest. Use these for text. If you write a raw font-size in a view, you are in an exception that needs a note, or you made a mistake.

iOS

Families

All three are system fonts. There is no download, no license file, and full support for Dynamic Type and optical sizes. They also align with SF Symbols.

SF Pro

family-sans

All UI: controls, labels, navigation, and body text.

.system(design: .default)

New York

family-serif

Screen titles, editorial moments, and empty states.

.system(design: .serif)

SF Mono

family-mono

Data, times, identifiers, and figures that you compare.

.system(design: .monospaced)

Screen titles use New York, not SF Pro. That is the cheapest way to give the system a face. Body text stays on SF Pro. People expect SF Pro in an iOS list. A long serif paragraph fights that habit for no gain.

SF Pro Rounded is out of the system. It reads as soft in a way that does not match the house. If a future app needs it, document the exception. Do not add a fourth default family.

Weight band

The system uses three weights: 400, 510, and 590. Bold (700) is only for serif display and title-1. Rank comes from contrast, then space. Size is for a heading, not for every rank. Weight is the last tool.

TokenwghtUse
weight-regular400Body, labels, and the default.
weight-medium510Emphasis in a paragraph, numeric values, and the active tab.
weight-semibold590Headline, buttons, and section headers.
weight-bold700Only display and title-1 in serif.

Do not call .bold() on body text. For emphasis in a paragraph, use weight-medium. If that is not enough, use text-primary against text-secondary.

Tracking

SF adjusts tracking at each size. Do not override it. Do not add tracking tokens to the semantic layer.

Two exceptions, both documented: family-mono below 13pt, where a little extra space helps figures; and Figma mockups, where the system does not apply tracking and you must add the table by hand.

Composite styles

The names are the iOS names, not translated. The map to .font(.body) is one to one. A family suffix appears only when the family is not the default.

These specimens use system stand-ins on the web: ui-sans-serif, ui-serif, and ui-monospace. On device, use SF Pro, New York, and SF Mono. Sizes are the Large Dynamic Type values, in points.

The kettle is already warm.

displayLarge Title · 34/41 · 700

Screen title. One per view, and not always.

The kettle is already warm.

title-1-serifTitle 1 · 28/34 · 700

Editorial section header.

The kettle is already warm.

title-2Title 2 · 22/28 · 590

Section header.

The kettle is already warm.

title-3Title 3 · 20/25 · 590

Subheader and card title.

The kettle is already warm.

headlineHeadline · 17/22 · 590

Emphasized row, button, and list header.

The kettle is already warm.

bodyBody · 17/22 · 400

Default body. The style used most often.

The kettle is already warm.

body-emphasisBody · 17/22 · 510

Emphasis inside body text.

The kettle is already warm.

body-serifBody · 17/22 · 400

Long reading: notes, articles, a forecast paragraph.

The kettle is already warm.

calloutCallout · 16/21 · 400

Secondary text that is still content.

The kettle is already warm.

subheadlineSubheadline · 15/20 · 400

Metadata and row subtitle.

The kettle is already warm.

footnoteFootnote · 13/18 · 400

Footnotes and contextual help.

The kettle is already warm.

caption-1Caption 1 · 12/16 · 400

Labels and counters.

The kettle is already warm.

caption-2Caption 2 · 11/13 · 400

The minimum. Use only if there is no other option.

07:42 18°

mono-bodyBody · 17/22 · 400

Inline data and identifiers.

07:42 18°

mono-footnoteFootnote · 13/18 · 400

Times, table figures, and timestamps.

Composition rules

  • Minimum size is 11pt. That is caption-2. If you need smaller type, the problem is density, not type.
  • Prefer two styles on one screen: one heading + body. Use text-primary against text-secondary, then weight, before you pick a new size. Three styles is the ceiling, counting the title. A page may have a heading. On iOS that heading can be title-2. On the web the heading uses the same 17px size as body.
  • Quiet chrome uses Neutral 600. Disabled controls use Neutral 400. Do not pick a new size to make them recede. Active text stays stronger.
  • Do not mix families on the same line. Serif and sans can share a screen. They must not share a line. The only exception is mono figures inside a paragraph, and only when the figures are data.
  • Left align. Do not justify.
  • Reading length on iPhone is 35–50 characters. On a wide window, cap the column. Do not invent new sizes for iPad.
  • caption-2 at 11pt is not safe with text-tertiary. Use text-secondary or text-primary.
  • Tabular figures for any number that you compare in a column. That is .monospacedDigit() on SF Pro, not family-mono. Use mono for identifiers and code.

Dynamic Type

Use the system styles. Do not hard-code a size. .font(.body) grows. .font(.system(size: 17))does not.

Space and icon sizes that sit next to text must grow too. Use @ScaledMetric relative to the text style they serve.

A layout seen only at Large is not tested. Failures show at AX5, where body goes from 17pt to 53pt and a fixed row height breaks.

Web

Inter

Web is not a copy of the iOS ladder. Inter is the only sans family. There is no New York on the web. Three sizes cover reading, compact chrome, and captions. Mono uses the system ui-monospace stack as a stand-in.

Inter

family-sans-web

All web UI: the letter, docs, titles, and controls. Web only.

ui-monospace

family-mono-web

Data, times, identifiers, and figures that you compare. System stand-in on the web.

Web weight band

Inter uses 400 for body, UI, and captions. Headings use 500. That is one hundred points above body. Do not use 700 on the web. Rank comes from Neutral 1000 against Neutral 700, then this weight step. Size is not how rank is made.

TokenwghtUse
weight-regular400Body, UI, captions, and the default.
weight-heading500Titles and headings only. One hundred points above body.

Web tracking

Do not override tracking on Inter. Leave the default. Do not add tracking tokens to the semantic layer.

Web composite styles

Three sizes. Body and heading share 17/22. Heading is Neutral 1000 at weight 500. Body, UI, and caption are Neutral 700 at weight 400. The home letter uses body (.ntc-web-body).

The kettle is already warm.

bodyWeb · 17/22 · 400 · Neutral 700

Default reading text. The style used most often.

The kettle is already warm.

headingWeb · 17/22 · 500 · Neutral 1000

Page titles and section titles.

The kettle is already warm.

uiWeb · 15/20 · 400 · Neutral 700

Tables, toast, contrast chips, and compact chrome.

The kettle is already warm.

captionWeb · 12/16 · 400 · Neutral 700

Kickers, swatch captions, ramp hex, and type meta.

Open questions

  • Web is locked to three sizes. Do not add a fourth size to solve rank. If a screen is unclear, change color or space first.
  • New York on every iOS screen title is a strong bet. Prototype the first two apps with serif and with sans before you lock it.
  • There is no brand display face on iOS. System fonts have a ceiling: two house apps will look related, and they will also look like any careful iOS app. If differentiation matters more than cost, spend it on a display face for titles only. Keep SF Pro for UI.
  • Wide windows are not a reason for new sizes. When they arrive, decide a maximum column width.

Source of truth for the Swift aliases: foundations/Foundations.swift.