The scale, what each entry is for, and the two-sizes-per-screen rule.
Part of the Konjo design language. The laws in the spine apply here unless this chapter contradicts them.
Archivo, at two widths, and Bebas Neue is not one of them. Kata's typeface is Archivo (OFL), chosen side by side in V1–V2 of the Kata interview over Inter, Geist and Hanken Grotesk:
- Text — Archivo at normal width, 400–700, for every word that is not display type.
- Display — Archivo ExtraCondensed (
wdth62) at 800/900, tracking 0, for headlines, hero numbers, stats and belt names: thehero,statement,metricanddisplaytokens. - Mono —
fontFamily.mono, for a value read character by character or copied (a URL, an ID, an API key). Never body copy, never a label.
Never set a fontFamily by hand. In the app, every Text and TextInput gets Archivo
automatically: a style's fontWeight is turned into the matching Archivo family
(src/theme/brandFont.ts), because React Native picks a
custom font by family, not weight. On the web, fontFamily.body / .display in the token file
point at the next/font Archivo variable; display adds font-stretch: 62%. The display cut is
generated by scripts/brand/fonts.py because the Expo font
package ships normal width only.
What was given up. The platform face (SF, Roboto) was the one a person had already tuned, needing no network and never flashing. Archivo is bundled, so it needs no network either; it still scales with Dynamic Type; and the app waits for it before first render, so it never flashes. Archivo is wider than SF, so a label that fit before can wrap — that is reflow, which the rules want, never truncation.
Bebas Neue is the wordmark only (brand).
The scale had size, weight, line height, transform and tracking and no family axis at all, while Studio's inventory prescribed a monospace copy-link box — so the one pattern that needed a second family could not cite one.
Konjo's voice is heavy condensed caps for display, plain sans for everything else.
Every entry should carry a line height, and 14 of the current 25 do not — the count and the
names are regenerated in
the token tables. eyebrow used to be
among them, which mattered because it is the only uppercase register and sits above every
section; it now carries one. This is why
ScreenHeader needs a hardcoded fontSize: 22 breakpoint hack under 360pt.
| Token | Size / weight / tracking | Line height | Use |
|---|---|---|---|
hero |
64 / 900 / −2.0 | 62 | A single enormous numeral — a count, a score, a percentage. Digits only. |
statement |
46 / 900 / −1.2 | 46 | The hero block's words — a class name, an event name. Uppercase via textTransform. |
display |
34 / 800 / −0.8 | 38 | Screen title |
title |
22 / 700 / −0.3 | 28 | Section and card titles |
rowTitle |
16 / 600 / 0 | 22 | List row primary line |
bodyLarge |
16 / 400 | 24 | Long-form reading |
body |
15 / 400 | 22 | Default |
caption |
13 / 400 | 18 | Secondary line, metadata |
micro |
12 / 500 | 16 | Dense metadata. Floor for non-label text. |
eyebrow |
11 / 800 / +1.2 uppercase | 14 | The one uppercase register (L5) |
label |
13 / 700 / +0.6 | 16 | Buttons, chips, badges. Sentence case. |
caption is 13, not 12, because 13px is already the most-used raw size in the codebase
(166 declarations) and beats the 15px body token. The scale should describe the app that
exists.
Two type sizes should carry any given screen's content. Things 3 — the most-praised app in the study for comprehension — uses exactly two on its home screen, and groups by inserting ~12pt of extra space rather than drawing a divider.
Chrome is exempt and does not count: the screen title, the hero's statement and eyebrow,
and the tab bar. The rule is about the body of the screen — the rows, cards and tiles below
the hero — where two sizes (rowTitle + caption) should do all the work. A screen whose
content needs five sizes is a screen that has not decided what matters.
The count is per region, not per screen. A screen made of one list is two sizes. A screen
made of five self-contained cards is two sizes inside each card — its card titles, its
labels and its footer links are that screen's chrome, exactly as a page title is a single
region's. A Studio dashboard therefore runs title for a card, then
rowTitle + caption inside it, plus eyebrow and label and possibly metric, and is
legal — because no one region is asking a reader to hold six sizes at once.
What the rule forbids is unchanged and is the thing worth checking: two sizes competing inside one region. A row with a 16/600 subject and a 15/500 second line has not decided what matters; a card title above a row is a hierarchy, not a competition.
The hero ratio. When a screen has a hero number, it is bigger than the screen title. In the reference set the hero runs roughly 1.8–2× the page title, and the number-to-label ratio is somewhere near 4:1. A "hero" that is 2pt larger than the surrounding text is not a hero.
Ratios from the reference set are indicative, not measured. They were read off downsampled App Store screenshots, several of them keystoned marketing mockups; the critic pass found individual measurements off by 35% to 3×. Use them for the shape of the relationship, never as a spec. The specs are Konjo's own tokens above.
The legacy entries
The scale above is what new work uses. The token file ships more — displayTitle 28/800,
pageTitle 28/500, sessionTitle 28/500 and headerTitle 15/500 among them — and they are
live because hundreds of call sites use them, not because they are choices.
A screen title is display 34/800. If you are picking between display, displayTitle,
pageTitle and sessionTitle, the answer is always display; the other three are the same
decision made three times before the scale existed. They are not marked @deprecated yet
because deprecating a token with hundreds of consumers before the migration is scheduled just
adds noise to every build — see governance.