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

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.

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 (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 (lines 74-81) and are referenced throughout 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, 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, 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, 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 script verifies that no forbidden families appear and all size/weight combinations match the hierarchy

Template files like 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:

<!-- 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 Central token table; defines the typography role hierarchy, font stacks, Korean fallback, and constraints
skills/diagram-design/SKILL.md High-level documentation with pre-output checklist enforcing the hierarchy (§9)
scripts/lint-skin.py Linter validating font family, size, and weight compliance
assets/template-full.html HTML template with placeholder <text> elements for role-based replacement
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 and the pre-output checklist in SKILL.md prevents hierarchy violations
  • Source of truth: all specifications live in 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, 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 linter will reject the diagram. 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 (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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →