TypeSentient
One ladder, from a table cell to a hero. Every step carries its size, its leading, its tracking and its typeface, so nobody has to feel their way to a decision that was already made.
Three typefaces, and none of them is something you pick. A step carries its face. Mono holds micro and data, serif begins at subhead at 24px and runs to the top, and the sans holds everything between.
ui and data are the same size and different steps, because they are different jobs. One
is a control; the other is an identifier that has to be read character by character. Numbers do not need it — Nunito's
digits are all one width, so a column of figures already lines up in the sans.
24px is not a taste. Sentient's stroke contrast is 2.28:1 against Nunito's 1.26, and its thinnest stroke is 47 units of a thousand. That hairline reaches one device pixel on a standard-density screen at 21.2px. Below that it renders patchy and the face reads as a fault rather than a choice; above it the same contrast is the reason to use it. So the boundary sits at the first step clear of the threshold, and it holds everywhere: every heading from 24px up is serif, in the product as much as on the site.
Nothing asks for a face. A component asks for a step, and the face arrives with it. That holds in the product and on the site alike: a subsection heading in a settings pane and one on a landing page are the same step, so they are the same face.
One limit, written down rather than discovered later. Sentient is a Latin face: 389 glyphs, stopping at Latin Extended-B, with no Cyrillic and no Greek. Odin ships in Latin scripts, so nothing is affected today. If that changes, the boundary is the lever — a face can only own the steps the language it is set in allows, and every step from 24px up would have to fall back to the sans in scripts Sentient does not cover.
Six steps, from a subsection heading to a hero. Everything at 24px and above, in the product as well as on the site.
Five steps, and most of the product. Every control, label, value and paragraph a person reads or clicks.
Two steps, both about exactness. Uppercase micro-labels, and identifiers where 0 and O must not be confused. Not for numbers — Nunito's figures are already tabular.
Thirteen steps, from 11px to 76px. There is no app scale and no website scale. A table cell in the product and a table cell on the marketing site are the same job, and a heading is a heading wherever it appears.
The distinction that matters is interface text against reading text, and both exist everywhere.
A marketing page has buttons; a settings pane has paragraphs. So the ladder holds both: ui at 13px
for what you scan, body at 17px for what you read.
The pixel figures below are at 100 per cent, which is where the app opens. Every size in the product is a rem multiplied by the density setting, so type moves with padding, gaps and radii rather than on its own — two notches
either way, holding its shape at every step. Spacing owns that setting.
Every step carries its own leading and tracking, and the serif steps carry different numbers from the sans ones. Sentient's x-height is 3.7 per cent larger than Nunito's, so it fills more of the line and its leading is raised to match. It is also fitted far more tightly — its sidebearings are 0.62 of Nunito's at equal apparent size — so it needs proportionally less negative tracking to sit right. Both numbers come out of the font files rather than off an eye.
Twelve steps and three weights, applied independently. A section header is body-tight at 600. A field label is ui at 500. A value beside it is ui at 400. Three different jobs, one step, no new names.
That is why weight is an axis rather than part of a token. Fold it in and every size needs a name per weight, and the names multiply faster than the sizes do.
Values, descriptions, table cells. Anything being read rather than identified
Field labels, nav items, active states, inline emphasis
Headings and buttons, on the sans steps only
A free axis would let anyone write a combination the fonts cannot render. Sentient ships at 400 and 500 — there is no SemiBold, so a 600 heading at 24px would be synthesised by the browser and look it. And 600 at 11px in the mono is a smear. So each band permits a range, and the range is checkable.
| Steps | Face | Weights | Default | Why |
|---|---|---|---|---|
| micro · data | JetBrains Mono | 400 · 500 | 400 | 600 fills the counters at 11px, and a bolded identifier reads as emphasis it does not have. |
| caption → lead | Nunito | 400 · 500 · 600 | 400 | The full range. This is where interface hierarchy is built. |
| subhead → hero | Sentient | 400 · 500 · 700 | 400 | The family has no SemiBold, so 600 is unavailable. 700 is for a hero at full viewport, nowhere smaller. |
A heading is not a size. It is a weight applied to the size the surrounding text already uses.
These two carry most of the text in the product, so their weights decide how the whole thing feels.
A label identifies a control. It sits beside a value at the same size and often the same colour, so weight is what separates them. 500 does that without shouting, and it leaves 600 meaning heading everywhere in the system. A settings pane should not read as twenty headings stacked down the page.
The heading component follows the same rule: 600 while the steps are sans, 500 at subhead and above,
where Sentient has no SemiBold to give.
A field label and a section header are different jobs and take different weights, so a component that renders both needs to know which one it is rendering. That is a prop, not a size.
Odin chat is the exception, and the only one. A conversation is long-form reading, and how someone wants to read for an hour is theirs to decide. Serif is the default; sans is one tap away. Everywhere else the face follows the step.
It is also the only place where two faces share a step, and that creates a problem: set at the same size they do not look the same size. Sentient's x-height is 0.502em against Nunito's 0.484em, and its ascenders and descenders are shorter, so it sits larger on the line at the same specified size.
The fix is not a second step and not a hand-tuned number. font-size-adjust: 0.484 tells the browser to render whichever face is in use at the size that gives Nunito's
x-height, working it out from that font's own metrics. Sentient lands at 96.4 per cent, so the 17px step renders at
16.4 and the two faces match. Swap the serif for another one day and it still holds.
Which segments moved most last week, and what changed in them?
Three moved by more than ten per cent. The largest was Lapsed buyers, up 4,120 people, nearly all of them since Tuesday. The other two are smaller and look seasonal. The same rise happened in the same week last year.
Seven of today's eleven tokens stop existing, because they were weights wearing the costume of a size. Two sizes change: reading text moves off 14px, where it was smaller than some of the headings above it, and the smallest label moves to the mono face it was already behaving like.
| Today | Becomes | Why | ||
|---|---|---|---|---|
| caption | 11 / 400 | --text-micro | 11 / 400 | Moves to the mono face. It was always a label, never prose. |
| label | 12 / 400 | --text-caption | 12 / 400 | Same size, honest name. |
| ui | 13 / 400 | --text-ui | 13 / 400 | Unchanged. The size was always right. |
| ui-emphasis | 13 / 500 | --text-ui | 13 / 500 | Not a token. A weight applied to one. |
| title-2xs | 13 / 600 | --text-ui | 13 / 600 | Not a token. A heading is a weight. |
| body | 14 / 400 | --text-body-tight | 15 / 400 | Was reading text at an interface size. Now it is reading text. |
| body-emphasis | 14 / 500 | --text-body-tight | 15 / 500 | Not a token. |
| title-xs | 14 / 600 | --text-body-tight | 15 / 600 | Not a token. |
| title-sm | 16 / 600 | --text-body | 17 / 600 | Not a token. |
| title | 20 / 600 | --text-lead | 20 / 600 | Not a token. |
| title-lg | 24 / 600 | --text-subhead | 24 / 600 | Not a token. |
Nothing here is visible to anyone using the product. Someone building a screen asks for a table cell or a page title, and the size, the leading, the tracking and the face arrive together. The only person who ever chooses a typeface is the one reading a conversation.
Printing should be invisible.
Beatrice Warde, The Crystal Goblet, 1930
Warde was arguing about wine glasses. A crystal goblet is better than a gold one because you can see through it to the wine, and type is the same: the better it is set, the less of it you notice and the more of what is behind it you do. Ninety-five years on, in a product read for hours at thirteen pixels, the argument has not moved.