Skip to content

Typography

Closed vocabulary of typeface roles, canonical CSS variables, hosting rules, weights, scale, fallback chain, and tests for typography in every Koder surface (web/landing, Flutter mobile/desktop, CLI/TUI). Self-hosted-first: webfonts ship as `.woff2` from the consumer's own origin; Google Fonts / jsDelivr / unpkg are forbidden.

Source spec: specs/fonts/typography.kmd

Roles (closed vocabulary)

Other Koder specs reference roles, never specific typeface names. Wave 1 reuses Inter + JetBrains Mono under OFL; Wave 2 introduces Koder Display; Wave 3 ships proprietary Koder Sans + Koder Mono.

sans

UI body, controls, navigation — the workhorse face. Default everywhere.

Wave 1: Inter · --kds-font-sans

The quick brown fox jumps over the lazy dog. 0123456789.

mono

Code, terminal output, numeric tables, IDs, and hashes.

Wave 1: JetBrains Mono · --kds-font-mono

fn run() -> Result<(), Error> { let n = 42; Ok(()) }

display

Headlines, hero text, posters, OG images. Wave 1 falls back to the sans face at heavy weights.

Wave 1: Inter 700/800 · --kds-font-display

Build the next generation.

serif

Long-form reading mode (optional, opt-in per surface).

Wave 1: ui-serif / Georgia · --kds-font-serif

Long-form reading benefits from quiet shapes and generous line-height.

Weights

The required weights per role. Variable fonts (Inter VF, JetBrains Mono VF) are preferred — a single .woff2 covers every weight in the family.

sans

  • 400 Koder Design System
  • 500 Koder Design System
  • 600 Koder Design System
  • 700 Koder Design System

mono

  • 400 Koder Design System
  • 700 Koder Design System

display

  • 700 Koder Design System
  • 800 Koder Design System

serif

  • 400 Koder Design System
  • 700 Koder Design System

Type scale

Use these px values for font-size; map to CSS custom properties when the surface has more than ~5 sizes. Line-heights follow the body/UI/headline/reading buckets.

12px caption Form follows function. line-height: 1.4
14px small Form follows function. line-height: 1.4
16px body, base Form follows function. line-height: 1.5
18px lead Form follows function. line-height: 1.5
24px h4 Form follows function. line-height: 1.2
32px h3 Form follows function. line-height: 1.2
48px h2 Form follows function. line-height: 1.2
64px h1 / display Form follows function. line-height: 1.2

Canonical CSS tokens

Declare these once at :root. Component CSS resolves font-family through the variables so a Wave-2/3 typeface swap is a single-line change.

:root {
  --kds-font-sans:    'Inter', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --kds-font-mono:    'JetBrains Mono', ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
  --kds-font-display: var(--kds-font-sans);
  --kds-font-serif:   ui-serif, Georgia, "Times New Roman", serif;
}

body, button, input, select, textarea { font-family: var(--kds-font-sans); }
code, kbd, pre, samp, .mono           { font-family: var(--kds-font-mono); }
h1, h2, h3, .display                  { font-family: var(--kds-font-display); }

Audit gates (T1-T7)

Every surface — landing, mobile, desktop, TV, web — must pass these checks before release. See specs/fonts/typography.kmd § Tests.

IDTest
T1grep for forbidden hosts (fonts.googleapis, fonts.gstatic, jsdelivr/unpkg font CDNs) returns empty.
T2Every font-family in CSS/Dart/templ resolves to a role in the table above or its fallback chain.
T3Every @font-face block includes font-display: swap.
T4Hero / critical pages emit <link rel="preload" as="font" type="font/woff2" crossorigin> for the body face.
T5Each shipped .woff2 is ≤ 50 KB per subset/weight (or carries an exception comment).
T6Variable face used when the upstream provides one (Inter VF / JetBrains Mono VF).
T7AAA contrast preserved across the fallback chain (system fonts pass tools/a11y-test/).