Web Typography — Fonts & Text Styling Guide
In this tutorial, you'll learn about Web Typography. We cover key concepts, practical examples, and best practices.
Web typography is the art and technique of arranging text on a website — choosing fonts, sizing, spacing, and layout to make content readable, accessible, and visually appealing.
What You'll Learn
- Font formats, loading strategies, and performance trade-offs
- Typographic scale, line-height, and responsive sizing
- Accessibility best practices for readable web text
Why It Matters
95% of web content is text. Good typography improves readability by 30% and directly impacts user engagement. Bad typography — tiny fonts, poor contrast, or slow-loading custom fonts — drives users away and hurts Accessibility scores.
Real-world use: Doda Browser renders thousands of different fonts from websites worldwide. Durga Antivirus Pro scans web pages for malicious content — clear typography in scan reports helps users understand threats quickly. The DodaTech learning platform uses a carefully chosen type scale to keep tutorials readable across devices.
Typography Foundations
flowchart LR A[Font Selection] --> B[Font Loading
& Formats] B --> C[Type Scale
& Sizing] C --> D[Line-height
& Spacing] D --> E[Responsive
Typography] E --> F[Accessibility
& Contrast] style A fill:#f90,color:#fff style C fill:#4af,color:#fff style E fill:#4a4,color:#fff style F fill:#f4a,color:#fff
Font Formats & Performance
| Format | Browser Support | Size (vs TTF) | Best For |
|---|---|---|---|
| WOFF2 | 96%+ | 30–50% smaller | Modern sites — use this by default |
| WOFF | 98%+ | 15–30% smaller | Fallback for older browsers |
| TTF/OTF | 99%+ | Full size | Legacy support, desktop use |
| EOT | IE only | Full size | Internet Explorer (deprecated) |
Variable Fonts
Variable fonts pack multiple weights and styles into a single file by using interpolation axes. One variable font file can replace 10–20 individual weights, reducing font-related page weight by 60–80%.
Example axes: wght (weight 100–900), wdth (width 75–125%), slnt (slant), ital (italic)
Implementing Web Typography
Example 1: Loading Fonts with font-display: swap
/* Load Inter variable font with optimal settings */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter.woff2') format('woff2');
font-display: swap;
font-weight: 100 900;
font-style: normal;
}
/* Preload critical font */
<link rel="preload" href="/fonts/Inter.woff2" as="font" type="font/woff2" crossorigin>
/* Fallback font stack */
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 16px;
line-height: 1.6;
color: #1a1a1a;
}
Expected output: The page renders immediately with a system font fallback (Segoe UI on Windows, Roboto on Android, system sans-serif on Mac). Once Inter.woff2 loads (typically within 200ms on a fast connection), the text swaps to Inter. The font-display: swap prevents invisible text while the font loads.
Example 2: Responsive Type Scale with CSS Clamp
/* Fluid typography scale using clamp() */
:root {
--text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-sm: clamp(0.875rem, 0.8rem + 0.3vw, 1rem);
--text-base: clamp(1rem, 0.9rem + 0.4vw, 1.125rem);
--text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
--text-xl: clamp(1.25rem, 1.1rem + 0.8vw, 1.75rem);
--text-2xl: clamp(1.5rem, 1.2rem + 1.2vw, 2.25rem);
--text-3xl: clamp(1.875rem, 1.4rem + 1.8vw, 3rem);
}
h1 { font-size: var(--text-3xl); line-height: 1.1; }
h2 { font-size: var(--text-2xl); line-height: 1.2; }
h3 { font-size: var(--text-xl); line-height: 1.3; }
p { font-size: var(--text-base); line-height: 1.6; }
/* Example of clamping in action */
.hero-title {
/* At 320px: ~28px. At 1440px: ~48px. No media queries needed. */
font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem);
font-weight: 800;
letter-spacing: -0.02em;
}
Expected output: On a 375px phone, h1 renders at 30px; on a 1440px desktop, it renders at 48px. No breakpoints needed. The text scales smoothly between those sizes. The line-height decreases for larger headings (1.1) and increases for body text (1.6), following typographic best practices.
Example 3: Full Typography System with CSS Custom Properties
/* Complete typography system */
:root {
/* Font families */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-serif: 'Merriweather', Georgia, 'Times New Roman', serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
/* Font weights */
--weight-normal: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
/* Letter spacing */
--tracking-tight: -0.025em;
--tracking-normal: 0;
--tracking-wide: 0.05em;
/* Line heights */
--leading-none: 1;
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--leading-loose: 2;
/* Max line length for readability */
--max-readable: 65ch;
}
/* Apply to elements */
article {
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--leading-relaxed);
max-width: var(--max-readable);
color: #1a1a1a;
}
code, pre {
font-family: var(--font-mono);
font-size: 0.9em;
background: #f4f4f4;
padding: 0.2em 0.4em;
border-radius: 3px;
}
pre {
padding: 1em;
overflow-x: auto;
line-height: var(--leading-normal);
}
/* Accessible text contrast */
.light-text {
color: #6b7280; /* Gray-500: 4.6:1 contrast on white — WCAG AA */
}
.dark-text {
color: #111827; /* Gray-900: 15:1 contrast on white — exceeds AAA */
}
Expected output: The article body renders in Inter at 16–18px with comfortable line spacing. Code snippets use a monospace font with subtle background highlight. The max-width: 65ch ensures lines don't exceed 65 characters, the optimal length for comfortable reading. Disabled or low-contrast text meets WCAG AA standards (at least 4.5:1 ratio).
Typography Accessibility Rules
| Rule | Why | Implementation |
|---|---|---|
| Minimum 16px body text | Smaller text forces users to zoom, breaks layouts | font-size: clamp(1rem, 0.9rem + 0.4vw, 1.125rem) |
| Line spacing 1.5× for body text | Improves readability and scanability | line-height: 1.5 minimum for body |
| Max line length 65–75 characters | Longer lines are hard to read | max-width: 65ch on text containers |
| Contrast ratio at least 4.5:1 | WCAG AA requirement for normal text | Check with tools like WebAIM Contrast Checker |
| Don't use colour alone to convey meaning | Screen readers can't see colour | Use icons, underlines, and patterns too |
| Allow user font scaling | Respect browser font size settings | Use rem not px for font-size |
| Focus indicators visible | Keyboard navigation requires visible focus | outline: 2px solid #2563eb on :focus-visible |
Common Typography Errors
Using px for font-sizes — Pixels don't scale with browser settings. Users who set their browser to 150% font size get broken layouts. Always use
remfor font sizes.1rem = 16pxby default but respects user preferences.FOUT (Flash of Unstyled Text) or FOIT (Flash of Invisible Text) — Custom fonts block rendering by default. Use
font-display: swapto show fallback text immediately and swap when the custom font loads.Loading too many font weights — Loading 5+ weights of a font adds 200KB+ to page weight. Use variable fonts or limit to 2–3 weights (regular, medium, bold). Lazy-load additional weights if needed.
Poor contrast on body text — Light gray text (
#999) on white background fails WCAG contrast checks. Use#1a1a1afor dark text on light backgrounds. Check with the WebAIM contrast checker.Line length too long — Text that spans the full width of a 1920px monitor is exhausting to read. Set
max-width: 65chon article containers. Onechunit equals one character width.Not defining fallback fonts — If a custom font fails to load and there's no fallback, the browser uses its default (often Times New Roman). Always provide a proper font stack ending with a generic family like
sans-seriforserif.
Frequently Asked Questions
Practice Questions
What is the difference between
font-display: swapandfont-display: block?swapshows fallback text immediately and swaps when the custom font loads.blockhides text for up to 3 seconds while waiting for the custom font, causing invisible text.Why should you use
reminstead ofpxfor font sizes?remunits respect the user's browser font size setting. If a user sets 150% font size,1rembecomes 24px instead of 16px.pxvalues ignore this setting, making text harder to read for users who need larger fonts.What is a variable font and why does it improve performance? A variable font packs multiple weights and widths into a single file using interpolation axes. One variable font can replace 10+ individual weight files, reducing font-related payload by 60–80%.
What is the optimal line length for readability? 50–75 characters per line. CSS
chunits measure character width, somax-width: 65chis the standard target for body text containers.How does
clamp()help with responsive typography?clamp(MIN, PREFERRED, MAX)sets a fluid size that scales between a minimum and maximum based on the viewport. It eliminates the need for multiple media queries for font sizes.
Challenge
Build a typography system for DodaTech tutorials: set up a variable font (Inter or similar), create a fluid type scale with clamp() for all heading levels (h1–h6) and body text, implement a monospace font for code blocks, ensure WCAG AA contrast for all text, and test readability on mobile (375px) and desktop (1440px). Measure the font loading performance with Lighthouse.
Real-World Task
Durga Antivirus Pro's scan report page uses 14px text with line-height: 1.2 and no fallback fonts — users report eye strain after reading reports. Redesign the typography: set body text to 16px minimum with line-height: 1.6, add a proper font stack, use font-display: swap for the primary font, set max-width: 70ch for report content, and ensure all threat severity labels use colour-plus-icon indicators for colour-blind users.
Related: CSS Preprocessors | Related: Responsive Design Guide | Related: Web Accessibility Guide
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro