# How Diagram-Design's Typography Hierarchy Works: A Complete Guide to Font Roles, Sizes & Constraints

> Master Diagram-Design's typography hierarchy. Learn font roles, sizes, and constraints for Instrument Serif, Geist Sans, and Geist Mono to create clear, consistent diagrams.

- Repository: [Cathryn Lavery/diagram-design](https://github.com/cathrynlavery/diagram-design)
- Tags: deep-dive
- Published: 2026-09-06

---

**Diagram-Design enforces a strict four-family, six-role typography hierarchy that all diagrams must follow, with Instrument Serif for titles, Geist Sans for node names, and Geist Mono for technical labels—governed entirely by [`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md).**

Diagram-Design's design system uses a **small, fixed hierarchy of font families, sizes, and weights** that governs every typographic decision in generated diagrams. This hierarchy is defined as a single source of truth in the style guide and enforced through automated linting and pre-output checklists.

## The Four Font Families in Diagram-Design

All typography in Diagram-Design rests on four carefully chosen font families, imported from Google Fonts and injected into every generated HTML file:

| Family | Google Font Import | Primary Usage |
|--------|-------------------|---------------|
| **Instrument Serif** | `family=Instrument+Serif` | Page **titles** (`title`) and **editorial callouts** (`callout`) |
| **Geist (Sans)** | `family=Geist` | Human-readable **node names** (`node-name`) |
| **Geist Mono** | `family=Geist+Mono` | Technical **sublabels**, **ports**, **URLs**, **field types** (`sublabel`, `arrow-label`, `eyebrow`) |
| **Noto Sans KR** | `family=Noto+Sans+KR` | **Korean fallback** for all Hangul text |

The complete font stack is injected via a single `<link>` element. In [`skills/diagram-design/references/style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/style-guide.md) (lines 85-88), this stack is automatically added to every diagram's HTML output.

## The Six Typographic Roles

Diagram-Design's typography hierarchy consists of **six distinct roles** that map to the four families. These roles are ordered by visual importance and have fixed, non-negotiable specifications:

| Role | Family | Size | Weight | Usage |
|------|--------|------|--------|-------|
| `title` | Instrument Serif | **1.75 rem** | 400 | Page-level H1 |
| `node-name` | Geist (sans) | **12 px** | 600 | Human-readable node labels |
| `sublabel` | Geist Mono | **9 px** | 400 | Ports, protocols, URLs, field types |
| `eyebrow` | Geist Mono | **7–8 px** | 500 (tracked 0.18 em, uppercase) | Type tags, axis labels |
| `arrow-label` | Geist Mono | **8 px** | 400 (tracked 0.06 em) | Arrow annotations |
| `callout` | Instrument Serif *italic* | **14 px** | 400 | Editorial asides |

These definitions appear in the **Typography** table of [`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md) (lines 74-81) and are referenced throughout [`SKILL.md`](https://github.com/cathrynlavery/diagram-design/blob/main/SKILL.md) (lines 93-100) as the "Typography (summary)" block.

## Hierarchy Rules and "Load-Bearing" Constraints

Diagram-Design's typography hierarchy follows four strict rules that maintain visual consistency across all diagrams:

1. **Title prominence**: The `title` role is the most prominent element and must use Instrument Serif—even when a brand's primary typography is all-sans, the title retains Serif for contrast.

2. **Node name weight**: The `node-name` role always uses Geist Sans at **weight 600**, making it immediately distinguishable from technical content.

3. **Monospace restriction**: Geist Mono is reserved **exclusively for technical content**—ports, commands, URLs—and never used as a generic "developer aesthetic" font for body text (see [`SKILL.md`](https://github.com/cathrynlavery/diagram-design/blob/main/SKILL.md), lines 104-105).

4. **Callout distinction**: The `callout` role uses *italic* Instrument Serif to set editorial text apart from functional diagram elements.

The hierarchy is **strictly enforced**: you cannot substitute families between roles. The sole exception is Korean text, which must fall back to Noto Sans KR because the primary families lack Hangul glyphs ([`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md), lines 89-96).

## Legibility Constraints and Minimum Sizes

Diagram-Design imposes hard constraints to ensure legibility across scripts:

- **Minimum Hangul size**: **12 px**—any Korean label smaller than this is unreadable and must be enlarged or removed ([`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md), lines 152-158)
- **No arbitrary scaling**: The sizes listed above are the only permitted sizes; the design system does not allow custom scaling
- **Mono isolation**: Geist Mono never replaces Geist Sans for `node-name` under any circumstance

## How Typography Is Applied at Render Time

When Diagram-Design generates an HTML diagram, it executes a three-step typographic pipeline:

1. **Font injection**: The Google Font link containing all four families is inserted into the document head
2. **Role mapping**: `<text>` elements receive `font-family`, `font-size`, and `font-weight` attributes matching their assigned role exactly
3. **Linter validation**: The [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) script verifies that no forbidden families appear and all size/weight combinations match the hierarchy

Template files like [`template-full.html`](https://github.com/cathrynlavery/diagram-design/blob/main/template-full.html) contain placeholder `<text>` elements that are replaced with role-appropriate values during rendering. Any violation—such as using Serif for a `node-name`—causes the linter to fail.

## Practical Implementation Example

The following SVG demonstrates each typographic role with exact specifications from [`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md):

```html
<!-- Font stack injected automatically by Diagram-Design -->
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&family=Noto+Sans+KR:wght@400;500;600&display=swap" rel="stylesheet">

<svg width="400" height="200" role="img" aria-labelledby="diag-title diag-desc">
  <title id="diag-title">Sample Diagram</title>
  <desc id="diag-desc">Illustrates the typography hierarchy</desc>

  <!-- Title: Instrument Serif, 1.75rem, weight 400 -->
  <text x="200" y="30" text-anchor="middle"
        font-family="'Instrument Serif', serif"
        font-size="1.75rem"
        font-weight="400">
    Architecture Overview
  </text>

  <!-- Node name: Geist Sans, 12px, weight 600 -->
  <text x="100" y="100" text-anchor="middle"
        font-family="'Geist', sans-serif"
        font-size="12px"
        font-weight="600">
    Service A
  </text>

  <!-- Sublabel: Geist Mono, 9px, weight 400 -->
  <text x="100" y="115" text-anchor="middle"
        font-family="'Geist Mono', monospace"
        font-size="9px"
        font-weight="400">
    http://api.example.com
  </text>

  <!-- Eyebrow: Geist Mono, 7px, weight 500, tracked, uppercase -->
  <text x="100" y="80" text-anchor="middle"
        font-family="'Geist Mono', monospace"
        font-size="7px"
        font-weight="500"
        text-transform="uppercase"
        letter-spacing="0.18em">
    API
  </text>

  <!-- Arrow label: Geist Mono, 8px, weight 400, tracked -->
  <text x="200" y="150" text-anchor="middle"
        font-family="'Geist Mono', monospace"
        font-size="8px"
        font-weight="400"
        letter-spacing="0.06em">
    Calls
  </text>

  <!-- Callout: Instrument Serif italic, 14px, weight 400 -->
  <text x="300" y="80" text-anchor="start"
        font-family="'Instrument Serif', serif"
        font-style="italic"
        font-size="14px"
        font-weight="400">
    Note: This service is deprecated.
  </text>
</svg>

```

For Korean labels, swap to: `font-family="'Geist', 'Noto Sans KR', sans-serif'` with a minimum 12px size.

## Key Files Governing Typography

| File | Purpose |
|------|---------|
| [`skills/diagram-design/references/style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/style-guide.md) | Central token table; defines the typography role hierarchy, font stacks, Korean fallback, and constraints |
| [`skills/diagram-design/SKILL.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/SKILL.md) | High-level documentation with pre-output checklist enforcing the hierarchy (§9) |
| [`scripts/lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/lint-skin.py) | Linter validating font family, size, and weight compliance |
| [`assets/template-full.html`](https://github.com/cathrynlavery/diagram-design/blob/main/assets/template-full.html) | HTML template with placeholder `<text>` elements for role-based replacement |
| [`scripts/verify-geometry.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-geometry.py) | Geometry verifier checking width budgets for Unicode characters including CJK |

## Summary

- **Four families** (Instrument Serif, Geist Sans, Geist Mono, Noto Sans KR) support **six roles** with fixed sizes and weights
- **No family substitution**: roles are locked to their assigned fonts; only Korean text triggers the Noto Sans KR fallback
- **12 px minimum for Hangul** ensures legibility; smaller Korean labels are rejected
- **Automated enforcement** via [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) and the pre-output checklist in [`SKILL.md`](https://github.com/cathrynlavery/diagram-design/blob/main/SKILL.md) prevents hierarchy violations
- **Source of truth**: all specifications live in [`skills/diagram-design/references/style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/style-guide.md)

## Frequently Asked Questions

### Can I use a different font family for the title if my brand requires sans-serif only?

No. According to [`SKILL.md`](https://github.com/cathrynlavery/diagram-design/blob/main/SKILL.md), the `title` role **must** use Instrument Serif even when a brand's primary typography is all-sans. This creates necessary visual contrast and is non-negotiable in the hierarchy.

### What happens if I use Geist Mono for node names instead of Geist Sans?

The [`lint-skin.py`](https://github.com/cathrynlavery/diagram-design/blob/main/lint-skin.py) linter will reject the diagram. [`SKILL.md`](https://github.com/cathrynlavery/diagram-design/blob/main/SKILL.md) (lines 104-105) explicitly prohibits using monospace fonts for `node-name`, reserving Geist Mono exclusively for technical content like ports, URLs, and field types.

### How does Diagram-Design handle Korean text in diagrams?

Korean text automatically falls back to **Noto Sans KR** at a **minimum 12 px size**. The primary font families (Instrument Serif, Geist) lack Hangul glyphs, so [`style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/style-guide.md) (lines 89-96) mandates this fallback. Any Korean label smaller than 12 px is considered unreadable and must be enlarged or removed.

### Where can I modify the typography hierarchy?

The hierarchy is **not user-configurable**. All role definitions live in [`skills/diagram-design/references/style-guide.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/style-guide.md) and are enforced by the skill's architecture. To request changes, you would need to fork the `cathrynlavery/diagram-design` repository and modify the source files directly.