Koder Design — Typography
fonts specs/fonts/typography.kmd
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.
Specification body
Koder Design — Typography
Canonical typography for the Koder Stack and every Koder product UI. The set of typeface roles is closed; the implementations behind each role evolve (Wave 1 reuses Inter + JetBrains Mono; Wave 2 introduces Koder Display; Wave 3 ships proprietary Koder Sans + Koder Mono). References to "the body font" or "the code font" in any other spec or component MUST resolve to a role defined here, not to a specific typeface name.
Roles (closed vocabulary)
| Role | Wave 1 implementation | Wave 2 target | Wave 2 ETA | Purpose |
|---|---|---|---|---|
| sans | Inter | Koder Sans | 2027 | UI body, controls, navigation, default everywhere |
| mono | JetBrains Mono | Koder Mono | 2027 | Code, terminal output, numeric tables, IDs, hashes |
| display | Inter @ 700/800 | Koder Display | 2026-Q4 | Headlines, hero text, posters, OG images |
| serif | ui-serif, Georgia, serif | (unchanged) | — | Long-form reading mode (optional, opt-in per surface) |
Wave 1 reuses upstream open-source typefaces under SIL Open Font
License 1.1; no Koder-owned typeface ships yet. Wave 2 introduces the
full Koder family (Sans + Mono + Display), designed in-house by the
owner with AI assistance, shipped under OFL with Reserved Font Name.
See meta/brand/koder-design#003-#030 for the roadmap; supersedes the
earlier commissioned-foundry plan in projects/koder-stack#128.
Canonical CSS variables (KDS-prefixed)
Every Koder web surface declares these once at :root and resolves
every font-family through them. Hard-coding the typeface name in
component CSS is forbidden — components consume the variable so the
Wave-2/3 swap is a single declaration 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); }
The legacy --kdr-* variables in tools/design-gen/assets/css/base.css
remain for non-typeface tokens until the broader rename sweep; for
typography always prefer --kds-font-*.
Hosting — self-hosted-first
- Webfont files MUST ship as
.woff2from the consumer's own origin (e.g.kds.koder.dev/assets/fonts/,<product>.koder.dev/assets/fonts/). @font-facedeclarations MUST point to a relative path on the same origin or to a Koder-owned*.koder.devhost. The following hosts are forbidden anywhere a Koder product loads:fonts.googleapis.com,fonts.gstatic.com,cdn.jsdelivr.net,unpkg.com,cdnjs.cloudflare.com, and every other third-party font CDN.font-display: swapis mandatory (system fallback shows immediately while the webfont streams in — mitigates FOIT).- For pages that critically depend on the webfont (hero / above-fold
text), the page shell MUST emit
<link rel="preload" as="font" type="font/woff2" crossorigin>.
Rationale: privacy (policies/analytics.kmd forbids third-party
beacons; Google Fonts is one), reliability (no third-party outage in
the critical path), and policies/self-hosted-first.kmd G1.
Weights
| Role | Required weights | Notes |
|---|---|---|
| sans | 400 (body), 500 (medium), 600 (UI emphasis), 700 (heading) | Variable font preferred when available |
| mono | 400 (code), 700 (highlight) | Variable font preferred |
| display | 700, 800 | Wave 2 may ship dedicated display face |
| serif | 400, 700 | Reader-mode only |
A surface MAY skip an unused weight; it MUST NOT introduce weights outside this table without amending this spec.
Variable fonts
Variable single-file .woff2 (e.g. Inter VF, JetBrains Mono VF) is
preferred over multiple static weight files: smaller total payload,
single HTTP request, finer weight control. When a variable file is
shipped, declare it with font-weight: 100 900; in @font-face and
drop the per-weight static files from the same family.
Subsetting
- Default subset:
latin+latin-ext(covers en-US + pt-BR + every European Latin-script locale). - Extra scripts (
cyrillic,greek,vietnamese,thai, …) ship as separate@font-face unicode-rangedeclarations only when a product ships a locale that requires them; otherwise they MUST NOT ship. - Subset payload budget: ≤ 50 KB per weight per script. Larger payloads require an exception comment in the consumer's CSS.
Type scale
Use these px values for font-size; map to CSS custom properties when
the surface has more than ~5 sizes.
12 (caption) · 14 (small) · 16 (body, base) · 18 (lead)
24 (h4) · 32 (h3) · 48 (h2) · 64 (h1 / display)
Line-heights:
- 1.5 — body, paragraphs
- 1.4 — controls, dense UI
- 1.2 — headlines, display
- 1.6 — long-form reading
Fallback chain — required
Every font-family declaration MUST end in a system fallback chain so
a webfont failure degrades gracefully. The chains above (--kds-font-*)
are the canonical chains; do not invent new ones per component.
Forbidden anywhere: Comic Sans MS, Papyrus, Times New Roman as a
primary face (Times is OK only inside the serif fallback chain).
Tests
| ID | Test |
|---|---|
| T1 | grep -rn 'fonts.googleapis|fonts.gstatic|jsdelivr.*font|unpkg.*font' <consumer>/ → empty |
| T2 | Every font-family in CSS/Dart/templ resolves to a role in the table above or its fallback chain |
| T3 | @font-face blocks include font-display: swap |
| T4 | Hero/critical pages emit <link rel="preload" as="font" ...> for the body face |
| T5 | Each shipped .woff2 ≤ 50 KB per subset/weight (or carries an exception comment) |
| T6 | Variable face used when the upstream provides one (Inter VF / JetBrains Mono VF) |
| T7 | AAA contrast preserved across the fallback chain (system fonts pass tools/a11y-test/) |
Cross-surface integration
- Web / landing / templ pages —
@font-face+--kds-font-*vars at:root(seetools/design-gen/assets/css/base.cssfor the canonical KDS implementation). - Flutter (koder_kit, mobile/desktop) — bundle the same
.woff2files underassets/fonts/and declare them inpubspec.yaml fonts:blocks; exposeKdsTextStyles.sans/mono/display/serifhelpers matching the role table. - JS (koder_web_kit) — re-export the
--kds-font-*values viaKdsTokens.font.sans/mono/...for consumers that bypass CSS. - CLI/TUI — terminals use the OS default mono; no spec is needed beyond "rely on the user's terminal font."
Roadmap
Wave 1 (now, 2026-05-11): Inter + JetBrains Mono self-hosted, OFL. Spec captured here; KDS landing reflects the canonical chains.
Wave 2 (2026-2027): Koder family — owner-led Sans + Mono + Display, designed in-house with AI assistance, OFL with Reserved Font Name. Implemented in three phases:
- Phase 1 (4-6 months): Koder Display ships in
--kds-font-display(headlines, hero, OG, splash). First visible ROI of the family. - Phase 2 (6-10 months): Koder Mono ships in
--kds-font-mono, substituindo JetBrains Mono. Koda-optimized (disambiguation, programming ligatures, tabular figures). - Phase 3 (10-18 months): Koder Sans ships in
--kds-font-sans, substituindo Inter. Workhorse body face, variable wght + opsz axes.
Tracked in
meta/brand/koder-design/backlog/{pending,in-progress,done}/tickets #003-#030. Supersedes the earlier commissioned-foundry plan inprojects/koder-stack#128.- Phase 1 (4-6 months): Koder Display ships in
References
meta/docs/stack/policies/self-hosted-first.kmdG1 (no third-party origin in critical path)meta/docs/stack/policies/analytics.kmd(no Google Fonts beacon)meta/docs/stack/rfcs/design-RFC-001-koder-design-system.kmdmeta/brand/koder-design/backlog/pending/003-pivot-owner-led-typeface-family.kmd— Wave 2 pivot decision recordmeta/brand/koder-design/backlog/pending/— full Wave 2 roadmap (#003-#030)projects/koder-stack/backlog/done/128-koder-display-commissioned-typeface-wave-2.md— superseded Wave 2 commission proposalprojects/koder-stack/backlog/pending/098-foundry-fonts-typography-store-scoping.md— the marketplace of fonts (distinct from this spec, which is the Koder Stack's own typography)tools/design-gen/assets/css/base.css— canonical KDS web implementationtools/design-gen/assets/fonts/— hosted.woff2files