Typography Handbook

A referential guide on best web typographic practices.

Introduction

What is typography?

Typography is more than just what fonts you use. It is everything that has to do with how text looks – font size, line length, colour, and subtler things like the whitespace around a text. Good typography sets the tone of your written message and helps reinforce its meaning and context.

This handbook is based on Kenneth Wang’s original Typography Handbook, updated and restyled as an independent reference. It is meant to be short and concise – a quick reference rather than an in-depth guide. For deeper explanations, follow the Further Reading links in each section.

Typographic Design

Visual hierarchy

Visual hierarchy is the concept of organizing elements on a page in a way that establishes an order of importance, allowing readers to easily navigate the page and find relevant content. When done correctly, it should guide the reader’s eye throughout different sections of the page. Visual hierarchy is very prevalent in typography, and forms the basis of typographic design theory.

Consider the following typographic design of Alice’s Adventures In Wonderland, which clearly establishes a visual hierarchy:

Alice’s Adventures in Wonderland typographic design
Click here to view the web version of this design

Visual hierarchy can be broken down into 5 different parts:

  1. Size & Weight: Size and weight are the two easiest ways to create visual hierarchy. They easily indicate what is important, and readers' eyes naturally jump to them.
  2. Positioning: The title and author sitting above the rest of the text and centered shows its importance.
  3. Typeface: Contrasting typefaces can create a distinction between different elements and achieve visual hierarchy.
  4. Color: Setting important text in a different colour is a very easy way to create visual hierarchy. However, only do so sparingly as using colour everywhere causes it to lose its distinction.

Further Reading

Gestalt laws in typography

Gestalt principles, or gestalt laws, are rules of the organization of perceptual scenes. When we look at the world, we usually perceive complex scenes composed of many groups of objects on some background, with the objects themselves consisting of parts, which may be composed of smaller parts, etc. Scholarpedia

The two most important Gestalt Laws to understand in typography are the Law of Proximity and the Law of Similarity.

Law of proximity

The Law of Proximity states that humans perceive objects that are closer together as related objects, and vice versa. In typographic design, “proximity” refers to the amount of whitespace created by line height, margin, and padding. There should be a distinctive and noticeable amount of additional whitespace between two different sections.

Law of proximity diagram
Image source
Proximity example

Law of similarity

The Gestalt Law of Similarity states that entities that look similar tend to be grouped together. In typography, this means keeping your styles consistent on elements that serve the same function. Elements with different functions should not look similar to one another.

Further Reading

Fonts

Choosing fonts

Choosing fonts is a creative and emotional process. Different fonts convey different feelings, and you want a font that complements the tone of your text.

Further Reading

Using web fonts

In 2026 you only need woff2. EOT, TTF, and SVG formats were required for older browsers that no longer exist. The modern @font-face declaration is much simpler:

@font-face {
  font-family: 'Your Font';
  src: url('/fonts/your-font-300.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Your Font';
  src: url('/fonts/your-font-300-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

Further Reading

Font loading

The font-display property – added to the @font-face rule – is now the standard way to control font loading behaviour. It replaced the need for JavaScript-based solutions in most cases.

@font-face {
  font-family: 'Your Font';
  src: url('/fonts/your-font.woff2') format('woff2');
  font-display: swap;
}

The key values for font-display are:

Preloading critical fonts

For fonts used above the fold – your body font, your main heading – add a <link rel="preload"> in the <head> to tell the browser to fetch the file early, before it’s discovered in the CSS:

<link rel="preload"
  href="/fonts/your-font-400.woff2"
  as="font"
  type="font/woff2"
  crossorigin>

Fallback font matching

The visual jump when a fallback font swaps to a custom font – the FOUT – can be minimised by making your fallback font metrics as close as possible to your custom font. The @font-face descriptor size-adjust lets you scale the fallback to match:

@font-face {
  font-family: 'Fallback for Inter';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Inter', 'Fallback for Inter', sans-serif;
}

Use Fallback Font Generator or Font Style Matcher to find the right override values for your specific font pairing.

Further Reading

OpenType features

OpenType features are typographic options built into the font file itself – things like ligatures, kerning, small caps, and oldstyle figures. They are enabled via CSS.

p {
  font-kerning: auto;
  font-variant-ligatures: common-ligatures contextual;
  font-optical-sizing: auto;
  font-feature-settings: "kern", "liga", "clig", "calt";
}

Further Reading

Variable fonts

Variable fonts are a single font file that contains a continuous range of weights, widths, or other axes – rather than separate files for each weight. A variable font with a weight axis from 100 to 900 replaces nine separate font files with one.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Use any weight along the axis */
h1 { font-weight: 650; }
p  { font-weight: 390; }
small { font-weight: 320; }

Further Reading

System font stacks

System fonts – the fonts already installed on the user’s device – have improved dramatically. San Francisco on macOS and iOS, Segoe UI on Windows, and Roboto on Android are all well-crafted, highly legible typefaces. Using them means zero network requests, zero font loading flash, and text that feels native to the platform.

/* Modern system font stack */
body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
    'Helvetica Neue', Arial, sans-serif;
}

/* Or just: */
body {
  font-family: system-ui, sans-serif;
}

Further Reading

Web Style Guide

Relative units

Use relative units whenever possible.

html { font-size: 100% }
p { font-size: 1em }

@media (min-width: 64em) {
  html { font-size: 112.5%; }
}

Further Reading

Containers

The container, also known as the wrapper, encloses one or more other elements. It allows for grouping elements semantically, cosmetically, or in layout.

html { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; }

.container {
  max-width: 67rem;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

Further Reading

Reading measure

The measure is the length of a line of text. It is one of the most important – and most overlooked – factors in readability. A line that is too long causes the eye to struggle to find the start of the next line; too short and the constant returns become fatiguing.

The classical typographic guideline is 45–75 characters per line (CPL) for a single-column body text, with 66 characters often cited as the ideal. For multi-column layouts, 40–50 CPL per column is more appropriate.

/* A practical measure using ch units */
/* 1ch ≈ the width of the "0" character */

.content {
  max-width: 65ch;  /* approximately 65 characters wide */
  padding-inline: 1.5rem;
}

Further Reading

Font sizing

Use a modular scale to establish the proportional relationships between your type sizes – the principle is unchanged from 2016. What has changed is how you implement it responsively.

Modular scale in action
A modular scale in action. Image source

Fluid typography with clamp()

The clamp() function – now universally supported – replaces the responsive modular scale approach. Instead of defining separate type sizes at different breakpoints, you define a minimum size, a maximum size, and a fluid value that scales between them based on viewport width. This eliminates the jarring jumps at breakpoints entirely.

/* clamp(minimum, preferred, maximum) */

h1 {
  /* 2rem at small viewports, scales up to 4rem, fluid in between */
  font-size: clamp(2rem, 5vw + 1rem, 4rem);
}

h2 {
  font-size: clamp(1.5rem, 3vw + 1rem, 2.5rem);
}

p {
  /* Body text: 1rem minimum, never exceeds 1.25rem */
  font-size: clamp(1rem, 1.5vw + 0.5rem, 1.25rem);
}

Further Reading

Vertical spacing

Vertical spacing is created by line-height, margin, and padding. line-height should be unitless. Apply margins in a single direction on textual elements, preferably margin-bottom. Adhere to the Law of proximity.

Vertical rhythm

Vertical rhythm keeps vertical spaces between elements consistent, creating a visually relaxing experience and a feeling of familiarity.

Vertical rhythm illustration
Image source
body { line-height: 1.4; }

p {
  font-size: 1.25em;
  margin-bottom: 1.75rem; /* 1.4 × 1.25 */
}

h1 { font-size: 3em; margin-bottom: 3.5rem; }
h2 { font-size: 2em; margin-bottom: 1.75rem; }
h3 { font-size: 1.5em; margin-bottom: 1.75rem; }

.page-container { padding: 3.5rem 2rem; }

Bottom aligned baseline grid

The bottom aligned baseline grid is a stricter implementation of vertical rhythm. In web, text is vertically aligned to the center of the line-height. In print, text is aligned to the bottom of the baseline grid instead.

Baseline grid illustration
Image source

In modern CSS, the lh unit – equal to the current element’s line-height – makes baseline-relative spacing much simpler without needing a preprocessor library. Remember that vertical rhythm is a guideline, not a rule. It does not need to be pixel perfect.

Further Reading

Colour

Color provides a huge visual distinction and is an important part of typography.

Further Reading

Underlining

In a printed document, don’t underline. Ever. It’s ugly and it makes text harder to read. Practical Typography

On the web, underlines serve a specific purpose: indicating hyperlinks. The browser default underline sits too close to the baseline, cuts through descenders, and carries a fixed weight regardless of font size. Modern CSS gives you precise control over all three.

a {
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.12em;
  text-decoration-skip-ink: auto;
}

Optical underline weight

Using em units for text-decoration-thickness means the underline scales proportionally with the font size. 0.08em is roughly 1–2px at body sizes but naturally heavier at display sizes where a hairline would look too light.

Skip-ink

text-decoration-skip-ink: auto tells the browser to interrupt the underline where it passes through descender strokes on letters like g, j, p, q, and y. This is the single most important underline improvement – without it, descenders appear to cut through the line at any size.

Underline offset

Moving the underline below the baseline with text-underline-offset creates breathing room between text and line. 0.12em is a comfortable starting point – enough separation to be legible, close enough to remain clearly associated with the text.

Contrast and hover

Softening the underline colour creates a quieter default state, with a stronger underline on hover:

a {
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.12em;
  text-decoration-skip-ink: auto;
  text-decoration-color: rgba(0, 0, 0, 0.35);
  color: inherit;
}

a:hover {
  text-decoration-color: currentColor;
}

Editorial link styling

For body copy where you want links to blend into the text without disappearing entirely, combining a muted underline colour with color: inherit is the most readable approach. Reserve underlines for hyperlinks only – using them for decorative emphasis causes confusion.

Further Reading

Text wrapping

CSS now offers native control over how text wraps – solving two long-standing typographic problems that previously required JavaScript or manual intervention.

text-wrap: balance

Balanced wrapping distributes text evenly across lines, eliminating the single short word – the “widow” – that often appears alone on the last line of a heading.

h1, h2, h3, h4, blockquote {
  text-wrap: balance;
}

text-wrap: pretty

Pretty wrapping is designed for body copy. Rather than balancing the whole block, it focuses specifically on the last few lines – avoiding orphaned words at the end of paragraphs without the performance cost of full balance.

p, li {
  text-wrap: pretty;
}

Further Reading

Colour and accessibility

Colour contrast between text and background is a legal and ethical requirement in most contexts, not an optional consideration. The Web Content Accessibility Guidelines (WCAG) define minimum contrast ratios, and meeting them is expected in professional work.

WCAG contrast requirements

Beyond contrast ratios

The APCA model

The Advanced Perceptual Contrast Algorithm (APCA) is a newer contrast model that better reflects how human vision actually perceives contrast, accounting for font size, weight, and polarity (light-on-dark vs dark-on-light). It is proposed for WCAG 3.0. While not yet a formal standard, it is worth understanding – particularly for light-weight or small type where WCAG 2.x can be misleading in both directions.

Further Reading

Dark mode typography

Dark mode is not simply an inverted colour scheme – it requires deliberate typographic adjustments. Text that reads well on a white background often becomes uncomfortable in dark mode if you simply swap the colours.

Contrast softening

Pure white text on a pure black background creates an extreme contrast that causes halation – a perceived glow or bloom around the letterforms that makes extended reading fatiguing. Soften both ends of the scale.

@media (prefers-color-scheme: dark) {
  body {
    background-color: #121212;  /* not pure black */
    color: #e8e8e8;             /* not pure white */
  }
}

Font weight compensation

Light text on a dark background appears thinner and lighter than the same text on a white background – a perceptual effect caused by how the eye processes light. To maintain visual consistency across modes, you may need to increase font weight slightly in dark mode.

@media (prefers-color-scheme: dark) {
  body {
    font-weight: 400;
  }

  /* If using a variable font, fine-tune the weight axis */
  body {
    font-variation-settings: 'wght' 420;
  }
}

Spacing adjustments

Dark backgrounds can make text feel more compressed. Slightly increased line-height or paragraph spacing can improve readability, though this is a minor consideration compared to contrast and weight.

@media (prefers-color-scheme: dark) {
  p {
    line-height: 1.6; /* vs 1.5 in light mode */
  }
}

Implementing dark mode

Use the prefers-color-scheme media query to detect the user’s system preference. Define your colour tokens as CSS custom properties so they can be swapped cleanly.

:root {
  --bg: #ffffff;
  --text: #161616;
  --text-muted: #525252;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --text: #e8e8e8;
    --text-muted: #a0a0a0;
  }
}

body {
  background-color: var(--bg);
  color: var(--text);
}

Further Reading

Conclusion

Good web typography is still harder than it should be. The fundamentals here – hierarchy, rhythm, scale, color, loading – don’t change much, but the implementation details do. This edition attempts to keep those details current while staying true to the spirit of the original work.

Further reading is the real education. The resources listed throughout are the ones worth your time.

Further Reading