Typography
Display serif for personality, humanist sans for readability, mono for code. The combination creates warmth without sacrificing legibility.
Font Stacks
| Token | Stack | Role |
|---|---|---|
--font-display | 'Fraunces', Georgia, serif | Headings h1–h3, display copy |
--font-body | 'Instrument Sans', system-ui, sans-serif | Body copy, paragraphs |
--font-mono | 'JetBrains Mono', 'Courier New', monospace | Code, <pre> blocks |
Fraunces is an optical-size variable serif — warm and considered rather than generic. Instrument Sans pairs naturally: geometric enough to feel clean, humanist enough to not feel cold. Together they create the “familiar but differentiated” quality the system aims for.
Fraunces Optical Size — Critical Rule
Fraunces is a variable font with an optical size axis (opsz).
This axis must always be set explicitly — the font renders
differently at different optical sizes and will look wrong without it.
| Element | opsz value | Why |
|---|---|---|
h1 | 72 | Optimises letterform detail for large display sizes |
h2 | 24 | Section heading weight — balanced stroke contrast |
h3 | 20 | Subsection — slightly thicker strokes at smaller size |
h1 {
font-family: var(--font-display);
font-weight: 800;
font-variation-settings: 'opsz' 72; /* mandatory */
}
h2 {
font-family: var(--font-display);
font-weight: 700;
font-variation-settings: 'opsz' 24; /* mandatory */
}
h3 {
font-family: var(--font-display);
font-weight: 600;
font-variation-settings: 'opsz' 20; /* mandatory */
} Type Scale
A modular scale anchored at 16px base, stepping ~1.25× (major third).
| rem | px | Use |
|---|---|---|
0.75rem | 12px | Captions, labels, badges |
0.875rem | 14px | Small body, UI secondary |
1rem | 16px | Default body |
1.125rem | 18px | Large body, intro copy |
1.25rem | 20px | H3 base |
1.5rem | 24px | H2 base |
2.25rem | 36px | H1 base |
4.5rem | 72px | Display / hero |
Use clamp() for fluid heading sizes — no breakpoint jumps. The DocLayout already applies clamp() to all h1–h3 elements.
Heading Styles
| Element | Font | Weight | Size | opsz | Tracking |
|---|---|---|---|---|---|
h1 | Fraunces | 800 | clamp(2.5rem, 5vw, 4rem) | 72 | -0.02em |
h2 | Fraunces | 700 | clamp(1.5rem, 3vw, 2rem) | 24 | -0.02em |
h3 | Fraunces | 600 | clamp(1.1rem, 2vw, 1.35rem) | 20 | -0.01em |
h4 | Instrument Sans | 600 | 1rem | — | 0 |
h5, h6 | Instrument Sans | 600 | 0.875rem | — | 0.04em |
Body & UI Styles
| Role | Font | Weight | Size | Line height |
|---|---|---|---|---|
| Body | Instrument Sans | 400 | 16px | 1.7 |
| Body large | Instrument Sans | 400 | 18px | 1.7 |
| Body small | Instrument Sans | 400 | 14px | 1.7 |
| Label | Instrument Sans | 600 | 12px | — |
| Caption | Instrument Sans | 400 | 12px | 1.6 |
| UI / buttons | Instrument Sans | 500–600 | 13–14px | 1.6 |
| Code | JetBrains Mono | 400–500 | 13px | 1.6 |
Mono & Code
--font-mono is JetBrains Mono, loaded at weights 400 and 500.
Use it exclusively for code — never decorative text.
Coding ligatures (=>, !=, -->) are enabled by default.
To disable: font-variant-ligatures: none.
| Element | Context | CSS |
|---|---|---|
<code> | Inline code in prose | font-family: var(--font-mono); font-size: 0.875em |
<pre><code> | Code blocks | font-family: var(--font-mono); font-size: 0.8125rem; line-height: 1.6 |
Inline code uses em so it scales with surrounding text size. Block code uses rem for a fixed 13px baseline regardless of context.
Live demo — inline code
Set the background colour with background-color: var(--color-bg) and pair it with
color: var(--color-text) for proper contrast in both themes.
Live demo — code block
.card {
background: var(--color-surface);
color: var(--color-text);
border-radius: var(--radius-md);
padding: var(--space-lg);
/* ligatures: => != --> are on by default */
font-variation-settings: normal;
} Letter Spacing
| Value | Use |
|---|---|
-0.02em | Display, h1, h2 — tightens large headings |
0em | Default for body and most UI |
0.04em | H5/h6, small UI labels |
0.12em | Uppercase caps, overline labels |
Responsive Typography
| Breakpoint | H1 | H2 | Body |
|---|---|---|---|
| Mobile < 640px | 28px | 22px | 16px |
| Tablet 640–1079px | clamp(28px, 5vw, 52px) | clamp(22px, 4vw, 36px) | 16px |
| Desktop ≥ 1080px | 36px | 24px | 16px |
Loading Fonts
Load via a <link> tag in <head> — not via @import url() inside CSS. The import approach delays rendering.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,300..900&family=Instrument+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
/> Usage Rules
- Fraunces for h1–h3 and display copy only — never for body text, UI labels, or navigation.
font-variation-settings: 'opsz'is mandatory on every Fraunces usage: h1 → 72, h2 → 24, h3 → 20.- Instrument Sans for everything interactive: buttons, inputs, nav, labels, UI text.
- JetBrains Mono for code only — never decorative. Inline
<code>:font-size: 0.875em(scales with context). Block<pre><code>:font-size: 0.8125rem; line-height: 1.6. - Body copy max-width: 70ch (
--width-prose). Never use Instrument Sans at weight 700+. - Fraunces minimum size: 16px — never smaller.
Typography Utility Classes
Opt-in utility classes that encode the full type scale — including mandatory Fraunces font-variation-settings —
so consumers never have to look up opsz values or clamp ranges.
Load alongside farn.css or farn-tokens.css:
<!-- npm -->
import 'farn-theme/typography';
<!-- CDN / manual -->
<link rel="stylesheet" href="farn-typography.css" /> Display & headings
Body & UI
Mono
CSS reference
/* Display */
.text-display {
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(3rem, 7vw, 4.5rem);
font-variation-settings: 'opsz' 72;
letter-spacing: -0.02em;
line-height: 1.05;
}
/* Heading 1 */
.text-h1 {
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(2.5rem, 5vw, 4rem);
font-variation-settings: 'opsz' 72;
letter-spacing: -0.02em;
line-height: 1.1;
}
/* Heading 2 */
.text-h2 {
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.5rem, 3vw, 2rem);
font-variation-settings: 'opsz' 24;
letter-spacing: -0.02em;
line-height: 1.2;
}
/* Heading 3 */
.text-h3 {
font-family: var(--font-display);
font-weight: 600;
font-size: clamp(1.1rem, 2vw, 1.35rem);
font-variation-settings: 'opsz' 20;
letter-spacing: -0.01em;
line-height: 1.3;
}
/* Heading 4 */
.text-h4 {
font-family: var(--font-body);
font-weight: 600;
font-size: 1rem;
line-height: 1.4;
}
/* Heading 5 */
.text-h5 {
font-family: var(--font-body);
font-weight: 600;
font-size: 0.875rem;
letter-spacing: 0.04em;
line-height: 1.4;
}
/* Body */
.text-body { font-family: var(--font-body); font-weight: 400; font-size: 1rem; line-height: 1.7; }
.text-body-lg { font-family: var(--font-body); font-weight: 400; font-size: 1.125rem; line-height: 1.7; }
.text-body-sm { font-family: var(--font-body); font-weight: 400; font-size: 0.875rem; line-height: 1.7; }
.text-label { font-family: var(--font-body); font-weight: 600; font-size: 0.75rem; }
.text-caption { font-family: var(--font-body); font-weight: 400; font-size: 0.75rem; line-height: 1.6; }
/* Mono */
.text-mono { font-family: var(--font-mono); font-weight: 400; font-size: 0.8125rem; line-height: 1.6; }