WEB DESIGN

Typography Best Practices

18 min read Intermediate

Typography is arguably the most important element of web design. Over 90% of the web is text, so how you present that text has a massive impact on user experience, readability, and the overall feel of your site. Good typography is invisible — it lets readers focus on the content. Bad typography drives people away before they read a single sentence.

This tutorial covers practical typography principles you can apply to any web project, from choosing the right fonts to fine-tuning spacing with CSS.

Understanding Font Categories

Before choosing fonts, you need to understand the main categories and what each one communicates.

Serif Fonts

Serif fonts have small decorative strokes (serifs) at the ends of letter forms. Think Times New Roman, Georgia, or Merriweather. Serifs convey tradition, authority, and elegance. They are excellent for long-form body text in print but are also highly readable on screen at modern resolutions. Many news sites and blogs use serif fonts for body copy.

This sentence is set in Georgia, a classic serif font. Notice the small strokes at the ends of letters.

Sans-Serif Fonts

Sans-serif fonts lack decorative strokes, giving them a clean, modern appearance. Arial, Helvetica, and Inter are common examples. Sans-serif fonts dominate web design because of their clarity at all sizes and their contemporary feel. They work well for both headings and body text.

This sentence is set in Arial, a widely available sans-serif font. Clean lines, no decorative strokes.

Monospace Fonts

In monospace fonts, every character occupies the same width. Courier New, Fira Code, and JetBrains Mono are popular choices. Monospace fonts are essential for displaying code, terminal output, and tabular data. They should rarely be used for body text on general websites.

const greeting = "Hello, World!";

Display Fonts

Display fonts are decorative typefaces designed for large sizes — headlines, logos, and hero sections. They have strong personality but poor readability at small sizes. Use them sparingly and only for short text at large sizes. Never use a display font for body copy.

Choosing Fonts for Readability

When selecting fonts for a web project, readability should always be your primary concern. Here are the key factors to consider:

  • x-height: Fonts with a taller x-height (the height of lowercase letters like "x", "a", "o") are more readable at small sizes. Compare Verdana (tall x-height) with Garamond (shorter x-height).
  • Character distinction: Can you easily distinguish between I (capital i), l (lowercase L), and 1 (number one)? Good fonts make these clearly different.
  • Weight range: Choose fonts that offer multiple weights (regular, medium, semi-bold, bold) so you have flexibility for emphasis and hierarchy.
  • Language support: If your site serves international audiences, ensure the font supports the required character sets.
  • Performance: Each font file adds load time. Limit yourself to 2-3 fonts maximum, and only load the weights you actually use.

Font Pairing Principles

Most websites use two fonts: one for headings and one for body text. Effective font pairing creates visual contrast while maintaining harmony. Here are proven pairing strategies:

1. Serif Headings + Sans-Serif Body

This is the most classic and reliable pairing. The serif heading adds character and authority, while the sans-serif body ensures readability. Example: Playfair Display + Source Sans Pro.

2. Sans-Serif Headings + Serif Body

The inverse pairing works well for editorial and long-form content. The clean heading contrasts with the traditional body text. Example: Montserrat + Merriweather.

3. Same Family, Different Weights

The safest approach: use one font family for everything and create hierarchy through weight and size. Fonts like Inter, Roboto, or IBM Plex have enough weight options to make this work beautifully.

4. Contrast, Not Conflict

Pair fonts that are noticeably different, not subtly similar. Two similar sans-serif fonts will look like a mistake. Two contrasting fonts (one geometric, one humanist, or one serif with one sans-serif) will look intentional.

Tip: Stick to a maximum of two fonts on a website. If you need more variety, use different weights and sizes of those two fonts. Every additional font adds HTTP requests and increases page load time.

CSS Font-Family and Font Stacks

The font-family property accepts a prioritized list of fonts called a font stack. The browser uses the first available font in the list:

/* A well-constructed font stack */
body {
    font-family: 'Inter', 'Segoe UI', Roboto, 'Helvetica Neue',
                  Arial, sans-serif;
}

/* Serif stack */
.article-body {
    font-family: 'Merriweather', Georgia, 'Times New Roman', serif;
}

/* Monospace stack for code */
code, pre {
    font-family: 'Fira Code', 'JetBrains Mono', 'Cascadia Code',
                  Consolas, 'Courier New', monospace;
}

/* System font stack (fastest loading) */
.system-fonts {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
                  Roboto, Oxygen-Sans, Ubuntu, Cantarell,
                  'Helvetica Neue', sans-serif;
}

Always end your font stack with a generic family (serif, sans-serif, or monospace) as the ultimate fallback.

Using Google Fonts

Google Fonts provides hundreds of free, open-source fonts that you can use on any website. Here is how to add them to your project:

<!-- Method 1: Link in HTML head (recommended) -->
<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;500;600;700&family=Merriweather:wght@400;700&display=swap" rel="stylesheet">

<!-- Method 2: CSS @import (slower, use sparingly) -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
</style>
Tip: Always include display=swap in your Google Fonts URL. This tells the browser to show a fallback font immediately while the custom font loads, preventing invisible text during loading (known as FOIT — Flash of Invisible Text).

Font Size with rem and em

Using the right units for font size is crucial for accessibility and responsive design.

rem (Root em)

rem is relative to the root element's font size (usually 16px by default). It provides a consistent scale across the entire page and respects user browser settings:

/* Set a base for easy calculation */
html {
    font-size: 16px;  /* 1rem = 16px (browser default) */
}

body {
    font-size: 1rem;      /* 16px */
}

h1 { font-size: 2.5rem; }   /* 40px */
h2 { font-size: 2rem; }     /* 32px */
h3 { font-size: 1.5rem; }   /* 24px */
h4 { font-size: 1.25rem; }  /* 20px */
small { font-size: 0.875rem; }  /* 14px */

em (Relative to Parent)

em is relative to the parent element's font size. This makes it useful for components where sizes should scale proportionally, such as padding inside buttons:

/* em for proportional sizing within components */
.button {
    font-size: 1rem;
    padding: 0.5em 1.5em;  /* Scales with font size */
}

.button--large {
    font-size: 1.25rem;
    padding: 0.5em 1.5em;  /* Padding grows proportionally */
}

Line Height for Readability

Line height (leading) is the vertical space between lines of text. It is one of the most impactful settings for readability. Too tight and the text feels cramped; too loose and the eye loses track between lines.

/* Line height guidelines */
body {
    line-height: 1.6;      /* Body text: 1.5 to 1.8 */
}

h1, h2, h3 {
    line-height: 1.2;      /* Headings: 1.1 to 1.3 */
}

.caption {
    line-height: 1.4;      /* Small text needs less leading */
}

/* Use unitless values for line-height */
/* BAD:  line-height: 24px;  (doesn't scale) */
/* GOOD: line-height: 1.6;   (scales with font size) */

A general rule: the longer the line length, the more line height you need. Wide paragraphs benefit from 1.7-1.8 line height, while narrow columns can use 1.5.

Letter Spacing

Letter spacing (tracking) is the uniform space between all characters. Adjusting it can dramatically change the feel and readability of text:

/* Letter spacing patterns */
.uppercase-label {
    text-transform: uppercase;
    letter-spacing: 0.1em;   /* Uppercase NEEDS more spacing */
    font-size: 0.75rem;
    font-weight: 600;
}

h1 {
    letter-spacing: -0.02em;  /* Tight tracking for large headings */
}

body {
    letter-spacing: 0;        /* Normal for body text */
}

.spaced-heading {
    letter-spacing: 0.05em;   /* Slightly open for elegance */
}
Tip: Uppercase text always needs increased letter spacing. Without it, capital letters feel cramped and are harder to read. A value of 0.05em to 0.15em works well for most uppercase text.

Heading Hierarchy

A clear heading hierarchy guides readers through your content and is essential for accessibility (screen readers use heading levels to navigate). Each heading level should be visually distinct from the others.

/* A practical heading scale */
h1 {
    font-size: 2.5rem;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
    margin-bottom: 1rem;
}

h2 {
    font-size: 2rem;
    font-weight: 600;
    line-height: 1.2;
    margin-top: 2.5rem;
    margin-bottom: 0.75rem;
}

h3 {
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.3;
    margin-top: 2rem;
    margin-bottom: 0.5rem;
}

h4 {
    font-size: 1.25rem;
    font-weight: 500;
    line-height: 1.4;
    margin-top: 1.5rem;
    margin-bottom: 0.5rem;
}

/* Rule: Each heading should be ~125-150% of the next level down */

Responsive Typography with clamp()

The CSS clamp() function lets you create fluid typography that scales smoothly between a minimum and maximum size based on the viewport width. No media queries needed.

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

h1 {
    /* Min: 2rem, scales with viewport, max: 3.5rem */
    font-size: clamp(2rem, 5vw, 3.5rem);
}

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

h3 {
    font-size: clamp(1.25rem, 3vw, 1.75rem);
}

body {
    /* Body text: 1rem minimum, slight scaling, 1.125rem max */
    font-size: clamp(1rem, 1vw + 0.5rem, 1.125rem);
}

/* A complete fluid type scale */
:root {
    --text-xs:   clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
    --text-sm:   clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
    --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
    --text-lg:   clamp(1.125rem, 1rem + 0.6vw, 1.25rem);
    --text-xl:   clamp(1.25rem, 1rem + 1.2vw, 1.75rem);
    --text-2xl:  clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem);
    --text-3xl:  clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
}
Tip: The clamp() function is supported in all modern browsers. The middle value (preferred) typically uses viewport width units (vw) combined with a rem base for smooth scaling. Use a fluid type calculator to generate precise values.

Key Takeaways

  • Understand font categories (serif, sans-serif, monospace, display) and when to use each.
  • Prioritize readability: choose fonts with good x-height, character distinction, and multiple weights.
  • Limit yourself to two fonts and create contrast through pairing different categories.
  • Build proper font stacks with fallbacks, always ending in a generic family.
  • Use rem for font sizes to respect user preferences and ensure consistency.
  • Set line height between 1.5-1.8 for body text, using unitless values.
  • Always increase letter spacing on uppercase text.
  • Create a clear heading hierarchy where each level is visually distinct.
  • Use clamp() for fluid, responsive typography without media queries.