Skip to content
Chapter 18Systems6 min read

Typography Tokens

Build a single source of truth for font families, sizes, weights, line heights, and tracking across CSS, Tailwind, and Figma.

Typography tokens are the atomic units of a design system. Instead of hard-coding font-size: 17px in a component, you reference a semantic token like text-body-md or --font-size-base. When the brand refreshes or accessibility requirements change, you update the token once and the entire product inherits the change.

This chapter covers how to structure typography tokens, wire them into CSS custom properties, extend Tailwind, and mirror them in Figma so design and engineering stay aligned.

Why Tokens Beat Raw Values

Raw pixel values create drift. A designer sets body text to 16px in Figma; a developer uses 15px because it "looked better" in a sidebar; a marketing page uses 18px for readability. Within weeks, three slightly different body sizes exist across the product.

Tokens solve this by naming intent, not appearance:

| Raw value | Token name | Semantic meaning | | --- | --- | --- | | 16px / 1.5 | text-body-md | Default reading text | | 14px / 1.4 | text-body-sm | Secondary content, captions | | 32px / 1.15 | text-heading-lg | Page titles | | 12px / 1.3 | text-label | UI labels, badges |

Token Categories

A complete typography token set covers five dimensions:

Font Family

Define a small stack of families with fallbacks. Most products need no more than three roles: sans for UI, serif or display for marketing, and mono for code.

css
:root {
  --font-family-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-family-serif: "Source Serif 4", ui-serif, Georgia, serif;
  --font-family-display: "Instrument Serif", var(--font-family-serif);
  --font-family-mono: "Geist Mono", ui-monospace, monospace;
}

Font Size

Use a modular scale rather than arbitrary sizes. A major third ratio (1.25) or perfect fourth (1.333) produces harmonious steps from xs through 6xl.

css
:root {
  --font-size-xs: 0.75rem;    /* 12px */
  --font-size-sm: 0.875rem;   /* 14px */
  --font-size-base: 1rem;     /* 16px */
  --font-size-lg: 1.125rem;   /* 18px */
  --font-size-xl: 1.333rem;   /* ~21px */
  --font-size-2xl: 1.777rem;  /* ~28px */
  --font-size-3xl: 2.369rem;  /* ~38px */
  --font-size-4xl: 3.157rem;  /* ~51px */
}

Font Weight

Limit weights to what you actually load. Four weights cover most UI needs: regular (400), medium (500), semibold (600), and bold (700).

css
:root {
  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
}

Line Height

Line height tokens should be unitless ratios, not fixed pixel values. Ratios scale automatically when font size changes.

css
:root {
  --line-height-tight: 1.15;   /* Headings */
  --line-height-snug: 1.35;     /* Subheadings */
  --line-height-normal: 1.5;   /* Body text */
  --line-height-relaxed: 1.65; /* Long-form reading */
  --line-height-loose: 1.8;    /* Captions, small text */
}

Letter Spacing (Tracking)

Tracking tokens compensate for optical size. Large display type often needs negative tracking; small uppercase labels need positive tracking.

css
:root {
  --tracking-tighter: -0.03em;
  --tracking-tight: -0.015em;
  --tracking-normal: 0;
  --tracking-wide: 0.025em;
  --tracking-wider: 0.05em;
  --tracking-widest: 0.1em;
}

Primitive vs Semantic Tokens

Organize tokens in two layers:

Primitive tokens are the raw values (--font-size-base, --line-height-normal). They describe the scale itself.

Semantic tokens map roles to primitives (--text-body-size: var(--font-size-base)). They describe usage context.

This separation lets you swap the entire scale without renaming every component token. A dashboard might use --text-body-size: var(--font-size-sm) while a marketing site uses --font-size-base, but both reference the same semantic name in components.

css
:root {
  /* Primitives (defined above) */

  /* Semantic aliases */
  --text-display-size: var(--font-size-4xl);
  --text-display-leading: var(--line-height-tight);
  --text-display-tracking: var(--tracking-tighter);

  --text-heading-size: var(--font-size-2xl);
  --text-heading-leading: var(--line-height-snug);
  --text-heading-tracking: var(--tracking-tight);

  --text-body-size: var(--font-size-base);
  --text-body-leading: var(--line-height-normal);
  --text-body-tracking: var(--tracking-normal);

  --text-caption-size: var(--font-size-sm);
  --text-caption-leading: var(--line-height-loose);
  --text-caption-tracking: var(--tracking-normal);

  --text-label-size: var(--font-size-xs);
  --text-label-leading: var(--line-height-snug);
  --text-label-tracking: var(--tracking-widest);
}

Tailwind Configuration

Extend Tailwind's theme to consume your CSS variables. This keeps Tailwind utilities synchronized with your token file.

javascript
import type { Config } from "tailwindcss";

const config: Config = {
  theme: {
    extend: {
      fontFamily: {
        sans: ["var(--font-family-sans)"],
        serif: ["var(--font-family-serif)"],
        display: ["var(--font-family-display)"],
        mono: ["var(--font-family-mono)"],
      },
      fontSize: {
        xs: ["var(--font-size-xs)", { lineHeight: "var(--line-height-loose)" }],
        sm: ["var(--font-size-sm)", { lineHeight: "var(--line-height-normal)" }],
        base: ["var(--font-size-base)", { lineHeight: "var(--line-height-normal)" }],
        lg: ["var(--font-size-lg)", { lineHeight: "var(--line-height-snug)" }],
        xl: ["var(--font-size-xl)", { lineHeight: "var(--line-height-snug)" }],
        "2xl": ["var(--font-size-2xl)", { lineHeight: "var(--line-height-tight)" }],
        "3xl": ["var(--font-size-3xl)", { lineHeight: "var(--line-height-tight)" }],
        "4xl": ["var(--font-size-4xl)", { lineHeight: "var(--line-height-tight)" }],
      },
      letterSpacing: {
        tighter: "var(--tracking-tighter)",
        tight: "var(--tracking-tight)",
        normal: "var(--tracking-normal)",
        wide: "var(--tracking-wide)",
        wider: "var(--tracking-wider)",
        widest: "var(--tracking-widest)",
      },
    },
  },
};

export default config;

With this setup, text-base font-medium tracking-tight leading-snug resolves through your token layer. Changing --font-size-base updates every text-base instance site-wide.

Figma Variables

Mirror your code tokens in Figma so designers and developers reference the same values.

Structure your Figma variables:

  1. Create a Typography collection with modes for Desktop, Tablet, and Mobile if sizes differ by breakpoint.
  2. Add number variables for font sizes, line heights, and letter spacing.
  3. Add string variables for font family names (matching your loaded web fonts).
  4. Create Typography styles that bind to these variables: Display/Large, Heading/H1, Body/Regular, Caption/Default, Label/Uppercase.

Naming convention: Match Figma style names to your semantic tokens. If code uses --text-heading-size, Figma should have a style called Heading/H1 bound to the same value.

Composite Typography Tokens

Some design systems define composite tokens that bundle all five dimensions into a single reference:

css
.text-heading-lg {
  font-family: var(--font-family-sans);
  font-size: var(--text-heading-size);
  font-weight: var(--font-weight-semibold);
  line-height: var(--text-heading-leading);
  letter-spacing: var(--text-heading-tracking);
}

.text-body-md {
  font-family: var(--font-family-sans);
  font-size: var(--text-body-size);
  font-weight: var(--font-weight-regular);
  line-height: var(--text-body-leading);
  letter-spacing: var(--text-body-tracking);
}

Composite classes reduce repetition in JSX and ensure every heading uses the full token set, not just font-size.

Governance and Maintenance

Typography tokens require light governance to stay useful:

  • Version tokens alongside your design system releases. Document breaking changes when scale ratios shift.
  • Audit quarterly for hard-coded values that bypass tokens. Search your codebase for raw px font sizes.
  • Test at extremes — zoom to 200%, switch to dark mode, and verify tokens still produce readable contrast and spacing.
  • Keep the scale small. More than 10 font sizes or 6 font weights usually indicates unnecessary complexity.

Typography tokens are not bureaucracy — they are the contract between design intent and implementation. Invest an afternoon defining them once, and every future component inherits a coherent typographic voice.