<!-- https://getkonjo.com/design/foundations/type · source: docs/design/foundations/type.md -->

# Type

The scale, what each entry is for, and the two-sizes-per-screen rule.

> Part of the [Konjo design language](../konjo-design-language.md). 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](../2026-09-29-kata-founder-interview.md)
over Inter, Geist and Hanken Grotesk:

- **Text** — Archivo at normal width, 400–700, for every word that is not display type.
- **Display** — Archivo ExtraCondensed (`wdth` 62) at 800/900, tracking 0, for headlines, hero
  numbers, stats and belt names: the `hero`, `statement`, `metric` and `display` tokens.
- **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`](../../../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`](../../../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](../brand/identity.md)).

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](../../../.claude/skills/design/references/tokens.md). `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](../studio/dashboard.md) 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](../governance.md).
