Typography knowledge becomes productive when applied to real UI patterns — article layouts, product pages, UI components, and marketing sites. In this final lesson you will build four production-quality typographic systems from scratch: a long-form article, a marketing hero section, a UI component library text scale, and a documentation site prose style. Each example demonstrates how to combine everything from this chapter into a coherent, maintainable typographic system.
Typography Hierarchy Principles
| Principle | CSS Technique | Effect |
|---|---|---|
| Size contrast | Large gap between heading and body sizes | Clear visual hierarchy — readers know what is important |
| Weight contrast | Bold headings, regular body | Reinforces size hierarchy without colour |
| Colour contrast | Dark headings, muted body text | Draws eye to key information first |
| Spacing rhythm | Consistent margin multiples (8px, 16px, 24px) | Predictable vertical flow — professional feel |
| Measure control | max-width: 65ch on prose |
Comfortable reading without horizontal eye travel |
| Limited typefaces | Max 2 font families per page | Coherent, intentional design |
Text Colour Roles
| Role | Usage | Typical Value |
|---|---|---|
| Heading / primary | h1–h3, important labels | #0f172a — near-black |
| Body / secondary | Paragraph text, descriptions | #475569 — dark slate |
| Muted / tertiary | Captions, timestamps, helper text | #94a3b8 — medium slate |
| Placeholder | Input placeholder text | #cbd5e1 — light slate |
| Accent | Links, highlights, interactive labels | #4f46e5 — indigo |
| Danger | Error messages, warnings | #dc2626 — red |
Vertical Rhythm — Spacing After Elements
| Element | margin-bottom | Notes |
|---|---|---|
| h1 | 1em–1.5em | Large space after display heading |
| h2 | 0.75em | Moderate space — leads into content |
| h3 | 0.5em | Tight — closely followed by related content |
| p | 1em–1.5em | Consistent paragraph rhythm |
| h2 preceded by section break | margin-top: 2.5em | Large space above signals new section |
* + * (or the more targeted p + p, h2 + p) applies margin only when an element follows another element — never when it is the first child. This eliminates the need to manually zero out margin-top on first-child elements and creates natural, consistent spacing throughout the document.max-width: 65ch. This combination is battle-tested across millions of reading sessions and provides the best baseline for any content-focused site.Basic Example — Production Article with Full Typography System
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Lora:ital,wght@0,600;1,400&display=swap" rel="stylesheet">
<title>Typography in Practice</title>
<style>
html { font-size: 100%; }
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--font-ui: 'Inter', system-ui, sans-serif;
--font-prose: 'Lora', Georgia, serif;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: clamp(1.125rem, calc(1rem + 0.5vw), 1.25rem);
--text-2xl: clamp(1.5rem, calc(1rem + 2vw), 2.25rem);
--text-3xl: clamp(1.875rem, calc(1rem + 3vw), 3rem);
--text-display: clamp(2.5rem, calc(1.5rem + 5vw), 4.5rem);
--ink: #0f172a;
--body: #334155;
--muted: #64748b;
--subtle: #94a3b8;
--accent: #4f46e5;
--bg: #fffbf7;
}
body { background: var(--bg); color: var(--body); font-family: var(--font-ui); line-height: 1.5; }
/* ── Site header ── */
.site-header {
padding: 0 32px;
height: 56px;
display: flex; align-items: center; justify-content: space-between;
border-bottom: 1px solid #e7e5e4;
background: white;
}
.site-logo { font-weight: 800; font-size: 1rem; color: var(--ink); letter-spacing: -0.02em; }
.site-nav { display: flex; gap: 24px; }
.site-nav a { font-size: var(--text-sm); font-weight: 500; color: var(--muted); text-decoration: none; }
.site-nav a:hover { color: var(--ink); }
/* ── Article layout ── */
.article-wrapper { max-width: 720px; margin: 0 auto; padding: 48px 24px 80px; }
/* ── Kicker ── */
.kicker {
font-family: var(--font-ui);
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--accent);
display: flex; align-items: center; gap: 8px;
margin-bottom: 16px;
}
.kicker::before { content: ''; display: block; width: 24px; height: 2px; background: var(--accent); }
/* ── Article title ── */
.article-title {
font-family: var(--font-prose);
font-size: var(--text-display);
font-weight: 600;
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--ink);
margin-bottom: 24px;
max-width: 20ch;
}
/* ── Byline ── */
.byline {
display: flex; align-items: center; gap: 12px;
font-size: var(--text-sm);
color: var(--muted);
margin-bottom: 40px;
padding-bottom: 40px;
border-bottom: 1px solid #e7e5e4;
}
.byline-avatar {
width: 36px; height: 36px; border-radius: 50%;
background: linear-gradient(135deg, var(--accent), #7c3aed);
display: flex; align-items: center; justify-content: center;
color: white; font-size: 0.75rem; font-weight: 700; flex-shrink: 0;
}
.byline-name { font-weight: 600; color: var(--ink); }
/* ── Prose body ── */
.prose { font-family: var(--font-prose); font-size: var(--text-lg); line-height: 1.8; color: var(--body); }
.prose p { margin-bottom: 1.5em; }
.prose p + p { text-indent: 1.5em; }
.prose h2 {
font-family: var(--font-ui); font-size: var(--text-2xl);
font-weight: 700; line-height: 1.25; letter-spacing: -0.02em;
color: var(--ink); margin: 2.5em 0 0.75em; text-indent: 0;
}
.prose h3 {
font-family: var(--font-ui); font-size: var(--text-lg);
font-weight: 600; line-height: 1.35; letter-spacing: -0.01em;
color: var(--ink); margin: 2em 0 0.5em; text-indent: 0;
}
.prose blockquote {
font-style: italic; font-size: 1.15em; line-height: 1.6;
border-left: 3px solid var(--accent); padding: 0.5em 1.5em;
margin: 2em 0; color: var(--muted); text-indent: 0;
}
.prose code {
font-family: 'Courier New', monospace; font-style: normal;
font-size: 0.875em; background: #f1f5f9;
padding: 0.1em 0.4em; border-radius: 4px; color: #c2410c;
}
.prose a {
color: var(--accent);
text-decoration-line: underline;
text-decoration-color: #a5b4fc;
text-underline-offset: 3px;
text-decoration-thickness: 1px;
}
.prose a:hover { text-decoration-color: var(--accent); }
/* ── Tags ── */
.tag-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 48px; }
.tag { font-family: var(--font-ui); font-size: 0.75rem; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.06em;
padding: 4px 12px; background: #ede9fe; color: var(--accent);
border-radius: 9999px; text-decoration: none; }
</style>
</head>
<body>
<header class="site-header">
<span class="site-logo">Prose</span>
<nav class="site-nav">
<a href="#">Articles</a>
<a href="#">Topics</a>
<a href="#">About</a>
</nav>
</header>
<main class="article-wrapper">
<p class="kicker">CSS Typography</p>
<h1 class="article-title">The Typography Rules Every Developer Must Know</h1>
<div class="byline">
<div class="byline-avatar">JS</div>
<div>
<span class="byline-name">Jane Smith</span> ·
<span>June 12, 2025</span> ·
<span>8 min read</span>
</div>
</div>
<div class="prose">
<p>Typography is not just about choosing a pretty font. It is the craft of making text readable, beautiful, and appropriate for its purpose. The difference between amateur and professional web typography is rarely the typeface — it is the dozens of small decisions about spacing, sizing, and hierarchy.</p>
<p>When developers learn CSS, they tend to focus on layout — Flexbox, Grid, positioning. But the properties that govern how text looks and reads are just as powerful, and the payoff is immediate: well-typed content is simply more pleasant to read and more trustworthy to experience.</p>
<blockquote>Typography is what language looks like. — Ellen Lupton</blockquote>
<h2>The Measure Problem</h2>
<p>Ask any typographer what single change has the biggest impact on readability, and most will say: control the measure. The measure is the width of a line of text. Too wide, and the eye loses its place between lines. Too narrow, and the frequent line breaks disrupt reading flow.</p>
<p>In CSS, <code>max-width: 65ch</code> is your best friend. The <code>ch</code> unit is relative to the "0" character in the current font — approximately one character wide. Sixty-five characters per line sits squarely within the typographically optimal 55–75 character range proven by reading research.</p>
<h3>Line Height Is Not Just Aesthetic</h3>
<p>A <code>line-height</code> below 1.4 causes readers to re-read lines. Above 1.9, lines feel disconnected. The 1.6–1.75 range — set as a unitless multiplier for correct inheritance — is the professional baseline for body text on screens.</p>
</div>
<div class="tag-list">
<a href="#" class="tag">CSS</a>
<a href="#" class="tag">Typography</a>
<a href="#" class="tag">Design</a>
<a href="#" class="tag">Frontend</a>
</div>
</main>
</body>
</html>
How It Works
Step 1 — Two Typefaces, Clear Roles
Lora (serif) handles the article title and body — its editorial character suits long-form reading. Inter (sans-serif) handles all UI chrome: header, kicker, headings within the article, byline, tags. Two fonts, one for content one for interface, is a classic and reliable pairing strategy.
Step 2 — Kicker with Decorative Line
The kicker uses ::before pseudo-element via display: flex; align-items: center with a content: '' block element styled as a horizontal rule. This creates the coloured line + label combination common in editorial design without extra HTML elements.
Step 3 — Blockquote Gets Italic Serif Treatment
The blockquote inherits the Lora serif font from the prose container and adds font-style: italic. Combined with the left border, muted colour, and indented text, it creates a visually distinct pull-quote that works typographically with both the display and body text.
Step 4 — p + p text-indent for Print Paragraph Style
The adjacent sibling selector p + p applies text-indent: 1.5em only to paragraphs that follow another paragraph — not the first in a section. The section headings break the sibling chain, so paragraphs immediately after a heading correctly have no indent.
Step 5 — Link Underlines Use the Modern API
Links in the prose use text-decoration-color: #a5b4fc (a light indigo) at rest and transition to #4f46e5 on hover. The offset keeps the underline away from descenders. This pattern provides an accessible non-colour indicator while being more refined than the browser default underline.
Real-World Example: Marketing Page Hero Typography
/* marketing-hero.css */
:root {
--font-display: 'Inter', system-ui, sans-serif;
--display-size: clamp(2.75rem, calc(1.5rem + 6vw), 5.5rem);
--sub-size: clamp(1.125rem, calc(1rem + 1vw), 1.5rem);
}
.hero {
min-height: 90vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 80px 24px;
background: radial-gradient(ellipse at 50% 0%, #312e81 0%, #1e293b 60%);
}
/* Eyebrow label */
.hero-eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(165, 180, 252, 0.12);
border: 1px solid rgba(165, 180, 252, 0.2);
color: #a5b4fc;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 6px 14px;
border-radius: 9999px;
margin-bottom: 24px;
}
/* Gradient headline */
.hero-headline {
font-family: var(--font-display);
font-size: var(--display-size);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.04em;
max-width: 16ch;
margin-bottom: 24px;
background: linear-gradient(135deg, #ffffff 30%, #a5b4fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Subheadline */
.hero-sub {
font-size: var(--sub-size);
line-height: 1.6;
color: #94a3b8;
max-width: 52ch;
margin-bottom: 40px;
}
/* CTA row */
.hero-cta { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.btn-primary {
padding: 13px 28px;
background: #4f46e5;
color: white;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, transform 0.15s;
}
.btn-primary:hover { background: #4338ca; transform: translateY(-1px); }
.btn-ghost {
padding: 13px 28px;
background: transparent;
color: #e2e8f0;
border: 1.5px solid rgba(255,255,255,0.2);
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.btn-ghost:hover { border-color: rgba(255,255,255,0.4); background: rgba(255,255,255,0.05); }
Common Mistakes
Mistake 1 — Too many typefaces on one page
❌ Wrong — three or more typefaces create visual chaos:
h1 { font-family: 'Playfair Display', serif; }
h2 { font-family: 'Raleway', sans-serif; }
p { font-family: 'Merriweather', serif; }
.nav { font-family: 'Montserrat', sans-serif; }
✅ Correct — two families maximum; vary weight and style for hierarchy:
h1 { font-family: 'Playfair Display', serif; font-weight: 700; }
h2, p, .nav { font-family: 'Inter', sans-serif; }
h2 { font-weight: 700; }
p { font-weight: 400; }
Mistake 2 — Dark text on dark backgrounds without enough contrast
❌ Wrong — muted text colour fails WCAG contrast ratio on dark backgrounds:
.dark-section { background: #1e293b; }
.dark-section p { color: #475569; } /* contrast ratio ~2.5:1 — fails AA (requires 4.5:1) */
✅ Correct — use light text colours on dark backgrounds:
.dark-section { background: #1e293b; }
.dark-section p { color: #94a3b8; } /* contrast ratio ~4.9:1 — passes AA */
Mistake 3 — Skipping vertical rhythm planning
❌ Wrong — arbitrary spacing between elements creates jarring visual flow:
h1 { margin-bottom: 37px; }
h2 { margin-bottom: 14px; }
p { margin-bottom: 22px; }
✅ Correct — use a consistent spacing scale (multiples of a base unit):
:root { --space: 8px; }
h1 { margin-bottom: calc(var(--space) * 4); } /* 32px */
h2 { margin-bottom: calc(var(--space) * 2); } /* 16px */
p { margin-bottom: calc(var(--space) * 3); } /* 24px */
Quick Reference
| Pattern | Key CSS | Notes |
|---|---|---|
| Article prose | Serif body at 1rem–1.125rem, 1.75 line-height, 65ch max-width | Timeless readable combination |
| Marketing hero | Display size via clamp(); gradient text; tight letter-spacing | -0.03em to -0.05em at large sizes |
| UI labels | 0.7–0.75rem; 700 weight; uppercase; 0.08–0.1em letter-spacing | Always test readability at final size |
| Vertical rhythm | Spacing in multiples of 8px or 1rem | Consistent scale feels professional |
| Type pairs | One serif display + one sans-serif UI | Two families maximum |
| Accessible links | text-decoration + text-underline-offset | Non-colour indicator required in body text |