Three families do the talking and a fourth only handles code. Anybody stretches for posters, Schibsted Grotesk reads for hours, Shantell Sans letters the comic balloons, and Geist Mono stays inside code blocks where it belongs. Every piece of text uses a role class, never a raw size.
The families#
Print it loud
.mk-type-display size clamp(3rem, 2.0714rem + 3.81vw, 6.5rem) leading 0.9Anybody, on its width axis. The standard voice is near-normal and upright for heads; the wide italic and the condensed are for three words or fewer on one line: posters, SFX and the volume dial.
Schibsted Grotesk reads for hours without complaining about it.
.mk-type-lede size clamp(1.25rem, 1.1571rem + 0.381vw, 1.5rem) leading 1.4Schibsted Grotesk for body, labels and controls, at 16 px or more with a generous leading.
Pow, it shipped by noon
.mk-type-letter-balloon size 1rem leading 1.42Shantell Sans, all caps, in balloons and captions only. Balloons stay short: 35 words is the page limit.
import { Button } from '@maxgrits/overprint-react/button';
.mk-type-code size 1rem leading 1.6Geist Mono appears in code blocks and inline code, nowhere else (D-D9). No mono headlines, no mono labels, no mono "tech vibes".
Roles#
The roles by family, each printed in its own class with its size token. Sizes are fluid where the role is.
Text: Schibsted Grotesk (13 roles)
| Role | Size | Leading | Sample |
|---|---|---|---|
.mk-type-lede | clamp(1.25rem, 1.1571rem + 0.381vw, 1.5rem) | 1.4 | Pebblepath |
.mk-type-statement | clamp(1.375rem, 1.2357rem + 0.571vw, 1.875rem) | 1.3 | Pebblepath |
.mk-type-body | 1.125rem | 1.55 | Pebblepath |
.mk-type-body-ui | 1rem | 1.5 | Pebblepath |
.mk-type-meta | 1rem | 1.4 | |
.mk-type-row-title | 1.125rem | 1.3 | Pebblepath |
.mk-type-field-label | 1rem | 1.3 | Pebblepath |
.mk-type-label | 0.875rem | 1.3 | Pebblepath |
.mk-type-control-sm | 1rem | 1 | Pebblepath |
.mk-type-control-md | 1.0625rem | 1 | Pebblepath |
.mk-type-control-lg | 1.125rem | 1 | Pebblepath |
.mk-type-nav | 1rem | 1 | Pebblepath |
.mk-type-code | 1rem | 1.6 | Pebblepath |
Display: Anybody (10 roles)
| Role | Size | Leading | Sample |
|---|---|---|---|
.mk-type-display | clamp(3rem, 2.0714rem + 3.81vw, 6.5rem) | 0.9 | Pebblepath |
.mk-type-head-section | clamp(2rem, 1.675rem + 1.333vw, 3.25rem) | 1 | Pebblepath |
.mk-type-head-card | clamp(1.5rem, 1.3607rem + 0.571vw, 2rem) | 1.05 | Pebblepath |
.mk-type-kicker | clamp(1.5rem, 1.3607rem + 0.571vw, 2rem) | 1.1 | Pebblepath |
.mk-type-numeral-xl | clamp(3.5rem, 2.9429rem + 2.2857vw, 5.5rem) | 0.9 | Pebblepath |
.mk-type-numeral-l | clamp(2.75rem, 2.5179rem + 0.9524vw, 3.625rem) | 0.9 | Pebblepath |
.mk-type-numeral-m | clamp(2rem, 1.9071rem + 0.381vw, 2.25rem) | 0.95 | Pebblepath |
.mk-type-numeral-s | 1.5rem | 1 | Pebblepath |
.mk-type-sfx | clamp(3.5rem, 2.2rem + 5.333vw, 8rem) | 0.85 | Pebblepath |
.mk-type-wordmark | 1.3125rem | 1 | Pebblepath |
Lettering: Shantell Sans (5 roles)
| Role | Size | Leading | Sample |
|---|---|---|---|
.mk-type-letter-balloon | 1rem | 1.42 | Pow, it ships |
.mk-type-letter-caption | 1rem | 1.5 | Pow, it ships |
.mk-type-letter-shout | 1.3125rem | 1.1 | Pow, it ships |
.mk-type-letter-whisper | 1rem | 1.42 | Pow, it ships |
.mk-type-letter-note | 1rem | 1.4 | Pow, it ships |
The second ink#
Display type of 2 rem and larger prints twice: the second ink lands off register as it arrives and stays there at
rest (D-D6, Max's call). It is one token switch, data-mk-second-ink, with three settings:
rest (default)
Fernlight
motion
Fernlight
off
Fernlight
Below 2 rem the offset reads as blur, so smaller type never prints twice. Quiet and more contrast turn it off too.
Fitting display lines#
A short display line can be fitted to its slot, so a poster word fills the width exactly: that is FittedLine. It never jumps on load, because the first paint is already close.
- On the server,
fitEm(text, role, voice)from@maxgrits/overprint-react/artmeasures a line in ems with Anybody's own advance widths and kerning (the server advance table, within 1.5 % of the browser). Pass the result toFittedLine estimate, and the first paint prints at the smaller of the role's size and the slot's width divided by the ems. estimateFit(em, width, lo, hi)does the same sum for any caller that knows its slot width, clamped between two sizes.FIT_CUTSholds each display role's width, weight and tracking per voice, the numbers both helpers use.- On the client, the fitter then measures the real words and sets the exact size; the reserved height never changes, and it refits when a reader's text spacing arrives (WCAG 1.4.12).
- Too long for the floor? The fitter goes below the role's floor, down to half of it and never under 16 px, before it would wrap (D-N140). Every other display line breaks a word that cannot fit inside its box instead of overflowing the page.
import { fitEm } from "@maxgrits/overprint-react/art";
import { FittedLine } from "@maxgrits/overprint-react/fitted-line";
export function PosterWord() {
return (
<FittedLine render={<p />} estimate={fitEm("Built to be tested", "display")}>
Built to be tested
</FittedLine>
);
}