Master UI typography fundamentals — choosing between serif and sans-serif, building a modular type scale, setting optimal line-height and letter-spacing, and pairing typefaces using CSS Google Fonts and variable fonts.
Typography is responsible for 95% of what users read on screen. It is the most foundational visual design skill, yet it is consistently undertreated in developer education. The difference between an amateur-looking interface and a professional one is often not the color palette or the component library — it is the typographic system.
UI typography has specific requirements that differ from print typography. Text must be readable across screens ranging from 320px wide phone displays to 2560px wide monitors, at distances ranging from 30cm (phone) to 60cm (desktop). It must work at 100% browser zoom and 200% zoom (accessibility requirement). It must be legible in both light and dark themes, on OLED screens with pure black backgrounds, and on cheaper displays with lower contrast.
This lesson builds a complete typographic system from first principles: choosing typefaces with intention, building a mathematical type scale, setting line-height for readability, and pairing two typefaces that create hierarchy without clashing.
Serif typefaces have decorative strokes (serifs) at the ends of letterforms. Humanist sans-serif typefaces have no serifs — clean, geometric or humanist stroke endings. In UI, the choice between them is functional, not aesthetic.
Sans-serif dominates UI for body text:
Sans-serif UI workhorses: Inter, Source Sans Pro, IBM Plex Sans, Nunito Sans, DM Sans
Serif works well for:
Serif choices for UI: Playfair Display (display use only), Lora (body reading), Merriweather (editorial)
1/* Loading Inter (sans-serif) for UI + Fraunces (serif) for display headings */
2@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Fraunces:opsz,[email protected],700;9..144,800&display=swap');
3
4/* Preload critical fonts in <head> for performance:
5<link rel="preload" href="inter.woff2" as="font" type="font/woff2" crossorigin>
6*/
7
8:root {
9 /* Two-family system: display for headings, text for body */
10 --font-display: 'Fraunces', Georgia, serif;
11 --font-text: 'Inter', system-ui, -apple-system, sans-serif;
12 --font-mono: 'Fira Code', 'Cascadia Code', monospace;
13}
14
15/* Apply font families by semantic role */
16body { font-family: var(--font-text); }
17h1, h2 { font-family: var(--font-display); }
18code, pre { font-family: var(--font-mono); }The key principle: use at most two typeface families. More creates visual noise. One family for display (headings, hero) and one for text (body, UI components) covers 100% of UI needs.
A modular type scale is a set of type sizes with consistent mathematical relationships. Each size is the previous size multiplied by a fixed ratio. The ratio determines how dramatic the scale feels — larger ratios create stronger hierarchy contrast; smaller ratios feel more subtle.
Common ratios:
Building a Minor Third (1.25) scale:
1:root {
2 /* Type scale: base 1rem, ratio 1.25 (Minor Third) */
3 /* formula: next = prev * 1.25 */
4 --text-2xs: 0.512rem; /* ~8px — do not use for readable text */
5 --text-xs: 0.64rem; /* ~10px — legal fine print, tooltips */
6 --text-sm: 0.8rem; /* ~13px — labels, captions, meta */
7 --text-base: 1rem; /* 16px — body text */
8 --text-lg: 1.25rem; /* 20px — lead paragraphs */
9 --text-xl: 1.5625rem; /* 25px — h4 / card titles */
10 --text-2xl: 1.953rem; /* 31px — h3 / section headings */
11 --text-3xl: 2.441rem; /* 39px — h2 / page titles */
12 --text-4xl: 3.052rem; /* 49px — h1 / hero headings */
13 --text-5xl: 3.815rem; /* 61px — display / super hero */
14}
15
16/* Heading element assignments */
17h1 {
18 font-family: var(--font-display);
19 font-size: clamp(var(--text-3xl), 5vw, var(--text-4xl));
20 font-weight: 800;
21 line-height: 1.05;
22 letter-spacing: -0.025em;
23 margin: 0 0 0.5rem;
24}
25h2 {
26 font-family: var(--font-display);
27 font-size: clamp(var(--text-2xl), 4vw, var(--text-3xl));
28 font-weight: 700;
29 line-height: 1.15;
30 letter-spacing: -0.02em;
31 margin: 0 0 0.75rem;
32}
33h3 {
34 font-family: var(--font-text);
35 font-size: clamp(var(--text-xl), 3vw, var(--text-2xl));
36 font-weight: 700;
37 line-height: 1.25;
38 letter-spacing: -0.01em;
39 margin: 0 0 0.75rem;
40}
41h4 { font-family: var(--font-text); font-size: var(--text-xl); font-weight: 600;
42 line-height: 1.3; margin: 0 0 0.5rem; }
43h5 { font-family: var(--font-text); font-size: var(--text-lg); font-weight: 600;
44 line-height: 1.4; margin: 0 0 0.5rem; }
45p { font-size: var(--text-base); font-weight: 400; line-height: 1.7; margin: 0 0 1rem; }Three properties control text readability at the character level: line-height (vertical space between lines), letter-spacing (horizontal space between characters), and measure (line length in characters).
Line-height rules:
Letter-spacing rules:
Measure (line length): The optimal line length for reading is 45-75 characters per line. Shorter = too much eye movement vertically; longer = eye loses its place returning to the next line.
1/* Optimal readability settings */
2body {
3 font-size: 1rem;
4 line-height: 1.7; /* generous for sustained reading */
5 letter-spacing: 0; /* Inter has optimized spacing at 16px */
6 color: var(--color-text-secondary);
7}
8
9/* Constrain article text width for optimal measure */
10.article-body p {
11 max-width: 68ch; /* ~65-70 characters per line */
12 /* ch unit = width of the '0' character in the current font */
13}
14
15/* Heading tracking rules */
16.display-hero {
17 font-size: clamp(3rem, 7vw, 5rem);
18 line-height: 1.0; /* ultra-tight for enormous text */
19 letter-spacing: -0.04em; /* optical correction at large sizes */
20}
21
22/* Uppercase badge tracking */
23.badge-text {
24 font-size: 0.6875rem;
25 font-weight: 700;
26 text-transform: uppercase;
27 letter-spacing: 0.08em; /* expand uppercase for legibility */
28}
29
30/* Responsive measure — narrower on mobile (screen is narrow) */
31.content-column {
32 width: min(68ch, 100%); /* min of 68ch or full width */
33}The ch unit is particularly useful for measure constraints: max-width: 68ch means exactly 68 characters of the current font, regardless of viewport or font size.
A two-typeface pairing system creates hierarchy through personality contrast. The display typeface (used for headings) provides character and expressiveness. The text typeface (used for body) provides neutrality and readability. Together they create a visual language that is both distinctive and comfortable to read.
Pairing principles:
Three strong pairings with CSS:
1/* Pairing 1: Fraunces (display serif) + Inter (text sans) — editorial authority */
2@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,[email protected],700;9..144,800&family=Inter:wght@400;500;600;700&display=swap');
3.theme-editorial {
4 --font-display: 'Fraunces', Georgia, serif;
5 --font-text: 'Inter', system-ui, sans-serif;
6}
7
8/* Pairing 2: DM Serif Display + DM Sans — refined, unified family */
9@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:wght@400;500;600;700&display=swap');
10.theme-refined {
11 --font-display: 'DM Serif Display', Georgia, serif;
12 --font-text: 'DM Sans', system-ui, sans-serif;
13}
14
15/* Pairing 3: Outfit (geometric sans) + Lato (humanist sans) — modern tech */
16@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@700;800&family=Lato:wght@400;700&display=swap');
17.theme-tech {
18 --font-display: 'Outfit', system-ui, sans-serif;
19 --font-text: 'Lato', system-ui, sans-serif;
20}
21
22/* Usage — same CSS, different personality based on theme class */
23.page-heading {
24 font-family: var(--font-display);
25 font-size: clamp(2rem, 5vw, 3.5rem);
26 font-weight: 800;
27 line-height: 1.1;
28 letter-spacing: -0.02em;
29}
30.page-body {
31 font-family: var(--font-text);
32 font-size: 1rem;
33 line-height: 1.7;
34 font-weight: 400;
35}Variable fonts reduce the number of font files needed. A variable font encodes multiple weights and styles in a single file, loaded with font-weight: 100 900 (the entire weight range):
1/* Variable font load — one file covers all weights */
2@font-face {
3 font-family: 'Inter';
4 src: url('/fonts/inter-variable.woff2') format('woff2');
5 font-weight: 100 900; /* entire weight axis available */
6 font-display: swap; /* show fallback while loading */
7 font-style: normal;
8}An article page implementing a full typographic system with modular scale, display and text font pairing, optimal line-height and measure settings, and responsive fluid type.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Typography System Demo</title>
7 <!-- Preload critical fonts -->
8 <link rel="preconnect" href="https://fonts.googleapis.com">
9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10 <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,[email protected],700;9..144,800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11 <style>
12 :root {
13 /* Typeface families */
14 --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
15 --font-text: 'Inter', system-ui, -apple-system, sans-serif;
16 --font-mono: 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
17
18 /* Type scale — Minor Third (1.25) */
19 --text-xs: 0.64rem;
20 --text-sm: 0.8rem;
21 --text-base: 1rem;
22 --text-lg: 1.25rem;
23 --text-xl: 1.5625rem;
24 --text-2xl: 1.953rem;
25 --text-3xl: 2.441rem;
26 --text-4xl: 3.052rem;
27
28 /* Color roles */
29 --color-bg: #ffffff;
30 --color-text-primary: #0f172a;
31 --color-text-secondary: #334155;
32 --color-text-muted: #64748b;
33 --color-border: #e2e8f0;
34 --color-surface-alt: #f8fafc;
35 --color-brand: #2563eb;
36 }
37 *, *::before, *::after { box-sizing: border-box; }
38 body {
39 margin: 0;
40 background: var(--color-bg);
41 font-family: var(--font-text);
42 color: var(--color-text-secondary);
43 font-size: var(--text-base);
44 line-height: 1.7;
45 -webkit-font-smoothing: antialiased;
46 }
47 .article {
48 max-width: 740px;
49 margin: 0 auto;
50 padding: 3rem 1.5rem;
51 }
52 /* Article header typography */
53 .article-tag {
54 display: inline-block;
55 font-size: var(--text-xs);
56 font-weight: 700;
57 text-transform: uppercase;
58 letter-spacing: 0.1em;
59 color: var(--color-brand);
60 margin-bottom: 1rem;
61 }
62 .article-title {
63 font-family: var(--font-display);
64 font-size: clamp(var(--text-2xl), 5vw, var(--text-4xl));
65 font-weight: 800;
66 color: var(--color-text-primary);
67 line-height: 1.08;
68 letter-spacing: -0.025em;
69 margin: 0 0 1.25rem;
70 }
71 .article-lead {
72 font-size: var(--text-lg);
73 color: var(--color-text-secondary);
74 line-height: 1.6;
75 margin: 0 0 1.5rem;
76 max-width: 60ch;
77 }
78 .article-meta {
79 display: flex;
80 gap: 1.25rem;
81 font-size: var(--text-sm);
82 color: var(--color-text-muted);
83 padding: 1.25rem 0;
84 border-top: 1px solid var(--color-border);
85 border-bottom: 1px solid var(--color-border);
86 margin-bottom: 2.5rem;
87 }
88 /* Body typography */
89 .article-content h2 {
90 font-family: var(--font-display);
91 font-size: clamp(var(--text-xl), 3vw, var(--text-2xl));
92 font-weight: 700;
93 color: var(--color-text-primary);
94 line-height: 1.2;
95 letter-spacing: -0.015em;
96 margin: 3rem 0 1rem;
97 padding-top: 2.5rem;
98 border-top: 2px solid var(--color-border);
99 }
100 .article-content h3 {
101 font-size: var(--text-xl);
102 font-weight: 700;
103 color: var(--color-text-primary);
104 line-height: 1.3;
105 letter-spacing: -0.01em;
106 margin: 2rem 0 0.75rem;
107 }
108 .article-content p {
109 font-size: var(--text-base);
110 line-height: 1.75;
111 color: var(--color-text-secondary);
112 margin: 0 0 1.5rem;
113 max-width: 68ch; /* optimal measure */
114 }
115 /* Inline code */
116 .article-content code {
117 font-family: var(--font-mono);
118 font-size: 0.875em;
119 background: var(--color-surface-alt);
120 color: #7c3aed;
121 padding: 0.1em 0.375em;
122 border-radius: 0.25rem;
123 }
124 /* Caption / small text */
125 .article-content figcaption {
126 font-size: var(--text-sm);
127 color: var(--color-text-muted);
128 text-align: center;
129 margin-top: 0.5rem;
130 }
131 </style>
132</head>
133<body>
134 <article class="article">
135 <span class="article-tag">Typography</span>
136 <h1 class="article-title">Why Your Typography System is the Foundation of Everything</h1>
137 <p class="article-lead">The gap between an amateur and a professional interface is rarely the color palette. It is almost always the typographic system. Here is how to build one that holds.</p>
138 <div class="article-meta">
139 <span>By Sarah Chen</span>
140 <span>June 5, 2026</span>
141 <span>8 min read</span>
142 </div>
143 <div class="article-content">
144 <p>Typography accounts for 95% of what users read on screen. It is the most consequential design decision you make — and the one most often delegated to default browser styles and the nearest sans-serif stack.</p>
145 <h2>The Type Scale</h2>
146 <p>A modular type scale gives every heading size a mathematical relationship to every other. The <code>clamp()</code> function makes those sizes fluid between viewport sizes without requiring breakpoints for every heading level.</p>
147 <h3>Choosing Your Ratio</h3>
148 <p>The Minor Third (1.25) ratio is the most versatile for product UIs. It creates visible hierarchy without the dramatic contrast of the Perfect Fifth (1.5), which works better for marketing pages than dense application interfaces.</p>
149 <p>The key insight about type scales: they are a system of relationships, not a list of sizes. Every size is meaningful relative to every other size. When you pick a size arbitrarily, you break the visual logic.</p>
150 </div>
151 </article>
152</body>
153</html>This article demonstrates the complete typographic system: the Fraunces display serif is used only for h1 and h2 headings — where its personality creates editorial authority. Inter handles all body, meta, and UI text — where its neutrality ensures readability. The type scale uses clamp() for fluid responsive sizes without breakpoints. Line-height is 1.75 for body (generous for sustained reading), 1.08 for the hero h1 (tight for large display text), and 1.2 for h2 (moderate for section headings). The max-width: 68ch on paragraphs enforces optimal measure. Letter-spacing is -0.025em on the hero heading (optical correction at large sizes) and 0.1em on the uppercase tag (expanded for all-caps legibility).
The next lesson covers spacing systems — the 4px and 8px grid, how to build a spacing scale using CSS custom properties, and why consistent spacing creates visual rhythm that makes interfaces feel polished rather than arbitrary.
Your progress is saved in this browser, on this device only.