Typography in Practice

▶ Try It Yourself

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
Note: The “lobotomised owl” selector * + * (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.
Tip: The most readable body text combination for digital screens is: a humanist sans-serif font (Inter, Nunito, Source Sans), 1rem base size, 1.6–1.75 line-height, #334155 or #475569 text colour, and max-width: 65ch. This combination is battle-tested across millions of reading sessions and provides the best baseline for any content-focused site.
Warning: Mixing more than two typefaces on a page creates visual chaos. A common professional pattern is: one display/serif font for large headings and one sans-serif for everything else (body, UI, labels). If you need variety, vary weight, size, and colour within a single family rather than adding more typefaces.

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.

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 */

▶ Try It Yourself

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

🧠 Test Yourself

An article page uses a dark sidebar background (#1e293b). Which text colour passes the WCAG AA minimum contrast ratio of 4.5:1 for normal text?





▶ Try It Yourself