Typography
Display serif for personality, humanist sans for readability, mono for code. The combination creates warmth without sacrificing legibility.
Font Families
Critical — Fraunces optical size: Every Fraunces usage must setfont-variation-settings: 'opsz' <value>. Use 72 for display/h1, 24 for h2, 20 for h3. Omitting it gives incorrect letterform rendering at every size.
Type Scale
.text-display · Fraunces 800 · clamp(3–4.5rem) · opsz 72 · lh 1.05
.text-h1 · Fraunces 800 · clamp(2.5–4rem) · opsz 72 · lh 1.1
.text-h2 · Fraunces 700 · clamp(1.5–2rem) · opsz 24 · lh 1.2
.text-h3 · Fraunces 600 · clamp(1.1–1.35rem) · opsz 20 · lh 1.3
.text-h4 · Instrument Sans 600 · 1rem · lh 1.4
.text-h5 · Instrument Sans 600 · 0.875rem · tracking 0.04em · lh 1.4
.text-body-lg · Instrument Sans 400 · 1.125rem · lh 1.7
.text-body · Instrument Sans 400 · 1rem · lh 1.7
.text-body-sm · Instrument Sans 400 · 0.875rem · lh 1.7
.text-label · Instrument Sans 600 · 0.75rem
.text-caption · Instrument Sans 400 · 0.75rem · lh 1.6
.text-mono · JetBrains Mono 400 · 0.8125rem · lh 1.6
Utility Classes
Opt-in classes that encode the full type scale — including mandatory Frauncesfont-variation-settings — so consumers never need 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; }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/display → 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.
Accent Emphasis
Use <em> inside any heading to highlight a word or phrase in the accent color. Works across all themes and surfaces — the accent token adapts automatically.
Build faster with Farn.
Compatible with any heading class — .text-display, .text-h1, .text-h2, etc.
CSS
em {
font-style: normal;
color: var(--color-accent);
}Reference
Heading styles spec
| 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.
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 |
All headings use clamp() — no hard breakpoint jumps. DocLayout applies these to the base h1–h3 elements.
UI Text Scale
Four size tokens for component CSS — inputs, labels, buttons, badges, metadata. Distinct from the heading/body utility classes above: those encode full typographic styles (family, weight, opsz, line-height); these are raw size values for overriding in component contexts. All component tokens (--btn-font-size,--input-font-size, etc.) reference these. Defined intokens/typography.css.
| Token | Value | px | Used for |
|---|---|---|---|
--text-2xs | 0.6875rem | 11 | Badge labels, overline captions (--badge-font-size) |
--text-xs | 0.75rem | 12 | Tooltips, metadata, small btn, role text (--tooltip-font-size, --btn-sm-font-size) |
--text-sm | 0.875rem | 14 | UI text: inputs, labels, buttons, tabs, tables (--input-font-size, --btn-font-size, --tab-font-size) |
--text-base | 1rem | 16 | Body text, accordion content (--accordion-font-size) |
Loading fonts
Self-host the WOFF2 files from the Farn repo and declare them with @font-face — no requests to third-party servers. See Getting Started › Load Fonts for the full declarations.
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 300 900;
font-display: swap;
src: url('/fonts/fraunces-latin-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Instrument Sans';
font-style: normal;
font-weight: 400 600;
font-display: swap;
src: url('/fonts/instrument-sans-latin.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400 500;
font-display: swap;
src: url('/fonts/jetbrains-mono-latin.woff2') format('woff2');
}