Typography
Two families do everything: Geist for the interface, JetBrains Mono for code, eyebrows, pricing,
and anywhere numbers need to line up. Sizes, weights, and spacing below are read live from
@elirobinson/tokens/tokens.css.
Families
| Token | Value |
|---|---|
--font-sans | var(--ds-font-sans-override, 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif) |
--font-display | var(--ds-font-display-override, 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif) |
--font-mono | var(--ds-font-mono-override, 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace) |
Geist covers Latin, Latin-Extended, Cyrillic, and Greek. The package currently loads both
families from Google Fonts via @import — the stylesheet's own comment flags this as a
placeholder: if you have licensed .woff2 files, self-host them and replace the import.
Each family reads a --ds-font-*-override before its own stack, which is how an app whose
framework loads the fonts re-points them. next/font in particular generates a hashed
family name and exposes it only as a CSS variable, so the literal 'Geist' above matches
nothing it loaded:
:root {
--ds-font-sans-override: var(--font-geist-sans);
--ds-font-mono-override: var(--font-geist-mono);
}Leave it unset and you get the stack shown above. See Installation for the cascade rule that applies to every other token.
The scale
A perfect-fourth-ish ramp on a 16px base. Body text never goes below --fs-md — the
smaller steps are for captions, eyebrows, and metadata, not paragraphs.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Practical tech, honestly built.
Weights
400 for body, 500 for UI labels, 600 for headings, 700+ for display peaks. Light (300) exists but earns its place rarely.
| Token | Value |
|---|---|
--fw-light | 300 |
--fw-regular | 400 |
--fw-medium | 500 |
--fw-semibold | 600 |
--fw-bold | 700 |
--fw-black | 800 |
Line height
Body text sits at 1.65 — relaxed and forgiving on purpose; accessibility-first beats dense. Display type tightens to 1.05.
| Token | Value |
|---|---|
--lh-tight | 1.05 |
--lh-snug | 1.2 |
--lh-normal | 1.5 |
--lh-relaxed | 1.65 |
Tracking
Display sizes tighten (−0.025em), body stays neutral, and mono eyebrows open up (+0.08em, uppercase).
| Token | Value |
|---|---|
--tr-tight | -0.025em |
--tr-snug | -0.015em |
--tr-normal | 0em |
--tr-wide | 0.04em |
--tr-caps | 0.08em |
Rules that keep it coherent
- Sentence case for headings. Title Case is reserved for product names and the tagline.
- Mono for numbers that compare — tables, pricing, stats — with tabular figures.
clamp()for display sizes so long translations don't break layouts (thet-display-*classes in tokens.css already do this).- Never justify text. Left-aligned, ragged right.