Most typography advice tells you to “use readable fonts” and “create hierarchy”. Useful, but not actionable. What designers and developers actually need are numbers: how many pixels, how much line height, how many characters per line, which contrast ratio, and where those figures come from.
This guide covers the web typography best practices that matter on screens, each tied to a concrete value and the reasoning behind it. The lens is accessibility first, because the WCAG success criteria already define most of the limits you should be designing inside. Meeting them does not make your type boring. It makes it work for everyone, on every device, at every zoom level.
Quick reference: the numbers at a glance
| Element | Recommended value | Why |
|---|---|---|
| Body text size | 16px minimum, 17-20px preferred on desktop | 16px is the browser default and the iOS no-zoom threshold for inputs |
| Body line height | 1.5 to 1.7 (unitless) | WCAG 1.4.12 Text Spacing requires content to survive 1.5 |
| Heading line height | 1.05 to 1.25 | Large type needs proportionally less leading to read as one block |
| Line length | 45-75 characters (about 60-68ch ideal) | WCAG 1.4.8 caps lines at 80 characters at AAA level |
| Text contrast | 4.5:1 normal text, 3:1 large text (AA) | WCAG 1.4.3; use 7:1 and 4.5:1 for AAA |
| Paragraph spacing | At least 2x the font size | WCAG 1.4.12 text spacing override value |
| Letter spacing | 0 for body, -0.01 to -0.03em for big headings, +0.05 to +0.1em for all caps | Optical compensation; layouts must also tolerate a 0.12em override |
| Type scale ratio | 1.2 (minor third) to 1.25 for UI, up to 1.333 for editorial | Keeps steps distinguishable without giant jumps on mobile |
| Zoom and reflow | Usable at 200% text zoom, reflow at 320 CSS px | WCAG 1.4.4 Resize Text and 1.4.10 Reflow |

1. Set body text at 16px minimum, and treat 18px as your comfort target
16px is not an arbitrary convention. It is the default font size of every major browser, which means it is the size millions of users have implicitly accepted or adjusted on purpose. Going below it is opting out of a well-tested baseline.
There is a second, very practical reason: on iOS, Safari automatically zooms the page when a user focuses a form field whose font size is under 16px. That zoom breaks layouts and disorients people. So inputs, selects and textareas should never be smaller than 16px.
Sensible ranges by context
- Long-form reading (articles, docs): 18-20px on desktop, 16-18px on mobile
- Interface text (dashboards, forms): 14-16px, with 16px for anything read in sequence
- Legal text, captions, metadata: 13-14px absolute floor, and never for content that matters
Note that mobile screens are held closer to the eye than desktop monitors, so a 16px phone reading experience is optically close to 18px at desk distance. That is why the mobile figure can be slightly lower, not because small text is fine on small screens.
2. Use 1.5 line height for body copy, and tighten it as type grows
WCAG success criterion 1.4.12 Text Spacing requires that your content stays readable and functional when a user forces line height to 1.5 times the font size. If you design at 1.2, your carefully measured cards and buttons will overflow the moment someone applies a readability stylesheet or browser extension.
Designing at 1.5 to 1.7 for body text from the start solves two problems at once: it passes the override test, and it visibly helps readers with dyslexia or low vision track from the end of one line to the start of the next.
Line height must scale inversely to font size. A 48px heading at 1.5 leaves a canyon of white space between lines and stops reading as a single unit. Use:
- Display and H1: 1.05 to 1.15
- H2 and H3: 1.2 to 1.3
- Body and lists: 1.5 to 1.7
- Small print: 1.6 to 1.8 (small text needs relatively more leading)
Always use unitless line height values. A unitless value multiplies the element’s own font size, so it inherits correctly. A fixed value like line-height: 24px will follow children of any size and eventually collide. A comparable breakdown sits on authordigital.co.nz.
3. Keep lines between 45 and 75 characters
Line length is the most frequently ignored rule on the web, mostly because full-width text looks tidy in a design tool. But reading is a series of saccades and line returns. Too long, and readers lose their place on the return sweep. Too short, and the rhythm breaks every few words.
The research-backed comfort zone is 45 to 75 characters per line, with 66 as a classic ideal. WCAG 1.4.8 (AAA) puts a hard ceiling at 80 characters for Latin scripts and 40 for CJK.
The cleanest way to enforce this in CSS is the ch unit, which is tied to the width of the zero character in your actual font:
.prose p,
.prose li {
max-width: 66ch;
}
This survives font size changes, zoom and viewport changes automatically, unlike a fixed max-width: 700px.

4. Hit 4.5:1 contrast for normal text and 3:1 for large text
This is the rule with the least room for interpretation. Under WCAG 2.2 SC 1.4.3 (Level AA):
| Text type | Level AA | Level AAA |
|---|---|---|
| Normal text (under 24px, or under 18.66px bold) | 4.5:1 | 7:1 |
| Large text (24px+, or 18.66px+ bold) | 3:1 | 4.5:1 |
| Icons, borders, focus rings, UI components | 3:1 (SC 1.4.11) | 3:1 |
Three practical consequences that designers routinely get wrong:
- Light grey body text fails. #757575 on white is exactly 4.6:1 and passes. #999999 on white is 2.8:1 and fails badly. If your “secondary text” colour is lighter than roughly #767676, rethink it.
- Placeholder text counts. Placeholders are text and must meet 4.5:1. Better still, use a visible label and skip the placeholder as a label substitute entirely.
- Text over images needs a guaranteed floor. Contrast is measured against the actual pixels behind each glyph. Add a solid overlay, a gradient scrim or a text background so the worst-case pixel still passes.
One nuance worth knowing: extreme contrast such as pure #000000 on pure #FFFFFF (21:1) can cause halation for some readers, particularly those with astigmatism or dyslexia. A near-black such as #1A1A1A on #FAFAFA still clears 15:1 and reads more comfortably for long text.
5. Build a type scale instead of choosing sizes one by one
Random sizes produce weak hierarchy and inconsistent rhythm. A modular scale multiplies a base size by a fixed ratio so every step is mathematically related. Typography in Web Design: A Complete Guide covers this in more depth.
For screens, a ratio of 1.2 (minor third) or 1.25 (major third) works best. Bigger ratios like 1.618 look dramatic in a hero mockup and become unmanageable on a 360px phone.
| Role | Mobile | Desktop |
|---|---|---|
| H1 / display | 30-34px | 44-56px |
| H2 | 24-26px | 32-36px |
| H3 | 20-22px | 24-28px |
| Body | 16-18px | 18-20px |
| Small / caption | 14px | 14-15px |
Cap your scale at 5 or 6 steps. If your design system has eleven text styles, half of them are decorative duplicates and nobody will apply them consistently.
6. Scale responsively with clamp(), not a pile of media queries
Fluid typography lets a size grow with the viewport between a hard minimum and a hard maximum. clamp() does it in one line and removes the jarring jump at breakpoints.
:root {
--text-body: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
--text-h3: clamp(1.25rem, 1.1rem + 0.7vw, 1.75rem);
--text-h2: clamp(1.5rem, 1.2rem + 1.4vw, 2.25rem);
--text-h1: clamp(1.875rem, 1.3rem + 2.6vw, 3.5rem);
}
body {
font-size: var(--text-body);
line-height: 1.6;
}
h1 {
font-size: var(--text-h1);
line-height: 1.1;
letter-spacing: -0.02em;
}
Two rules for doing this safely:
- Always express the minimum and maximum in
rem, neverpx. A clamp with avwcomponent and px bounds can prevent text from responding to browser zoom, which breaks SC 1.4.4. - Never use
vwalone for font size. At 320px it becomes unreadable, at 2560px it becomes absurd.

7. Respect user settings: rem units, 200% zoom, reflow at 320px
Someone who sets their browser default to 20px because they need it should get 25% larger text everywhere. That only happens if you size type in rem (relative to the root) rather than px. Hard-coded pixel sizes silently override a deliberate accessibility choice. This discussion raises a few points we skipped.
Three checks before you ship:
- Zoom text to 200% (WCAG 1.4.4). Nothing should be cut off, overlapped or unreachable.
- Set the viewport to 320 CSS px, or zoom a 1280px window to 400% (WCAG 1.4.10 Reflow). There should be no horizontal scrolling for reading text.
- Apply the text spacing override (WCAG 1.4.12): line height 1.5, paragraph spacing 2x font size, letter spacing 0.12em, word spacing 0.16em. Buttons with fixed heights and single-line cards are the usual casualties.
Also remember: never disable pinch zoom. Remove maximum-scale=1 and user-scalable=no from your viewport meta tag. It is a one-line accessibility failure that still appears in far too many templates.
8. Limit typefaces, limit weights, and load them fast
Typography is a performance decision as much as a visual one. Web fonts are render-blocking by nature and directly affect Largest Contentful Paint and Cumulative Layout Shift, both of which Google measures.
Design side
- Two typefaces maximum on most sites, often one is enough. A well-made variable font with a range of weights gives you all the contrast you need.
- Three to four weights maximum (for example 400, 500, 700). Each extra weight and style is another file.
- Never rely on synthetic bold or italic from the browser. Load the real cut or do not use it.
Engineering side
- Serve WOFF2 only. It is supported everywhere that matters and is roughly 30% smaller than WOFF.
- Self-host rather than calling a third-party font CDN. It removes a DNS lookup and a connection, and simplifies your privacy story under GDPR.
- Preload the one or two fonts used above the fold with
rel="preload" as="font" crossorigin. Preloading everything defeats the purpose. - Use
font-display: swapso text is readable immediately, then reduce the visible shift by matching fallback metrics withsize-adjust,ascent-overrideanddescent-overrideon a@font-facefallback definition. - Subset your files to the character ranges you actually publish. Dropping unused scripts often halves the payload.
- Prefer a variable font when you need three or more weights: one file usually beats four static ones.
9. Align left, avoid justification, and use uppercase sparingly
Some typographic habits inherited from print actively harm screen reading.
- Justified text: browsers have no real hyphenation and spacing intelligence, so
text-align: justifyproduces rivers of white space. WCAG 1.4.8 explicitly asks that text not be fully justified. Use left alignment for LTR languages (and right for RTL). - Centred text: fine for one to three lines such as a hero or a caption. Past that, every line starts in a different place and the return sweep becomes guesswork.
- ALL CAPS: removes the word-shape cues readers rely on and slows reading. Keep it to short labels, and add 0.05 to 0.1em letter spacing when you do. Also prefer CSS
text-transform: uppercaseover typing in caps so screen readers and copy-paste keep the real text. - Long italic runs: use italics for emphasis and titles, not paragraphs.
- Light weights at small sizes: a 300 weight at 14px thins the strokes and effectively lowers perceived contrast. Use 400 or heavier for anything under 18px.

10. Create hierarchy with weight and space, not size alone, and never with colour alone
Size is only one of your variables. Weight, colour, letter spacing, case and above all white space do the same job with less noise. A 20px bold label with generous space above it can outrank a 28px regular heading.
Two accessibility rules bound this work:
- Use real heading elements in order. One
h1per page, thenh2,h3, no skipped levels. Screen reader users navigate by heading list, so a styleddivthat looks like a heading is invisible to them. Visual size and semantic level are separate decisions: style with CSS classes, structure with HTML. - Do not signal meaning with colour alone (WCAG 1.4.1). This is why inline links inside body text should stay underlined. Colour alone fails for colour-blind users, and a link that is only distinguished by colour must also reach 3:1 contrast against the surrounding text. Underlining is simpler and clearer.
For the space side of hierarchy, apply proximity: a heading should sit closer to the paragraph it introduces than to the one it follows. Roughly 1.5 to 2x more space above a heading than below it makes structure readable before a single word is processed.
A pre-launch typography checklist
- Body text is 16px or larger, in rem units
- Body line height is at least 1.5; headings are tighter
- Text blocks are capped around 66ch and never exceed 80 characters
- All text passes 4.5:1, or 3:1 if large; placeholders and text on images included
- Type scale has 6 steps or fewer, applied consistently
- Fluid sizes use clamp() with rem bounds
- Page is usable at 200% zoom and reflows at 320px
- Text spacing override does not break any component
- Pinch zoom is enabled in the viewport meta tag
- Fonts are WOFF2, self-hosted, subset, with font-display: swap
- Heading levels are semantic and sequential
- Inline links are underlined
- Form inputs are at least 16px on mobile
Frequently asked questions
What are the best practices for typography in web design?
Start body text at 16px minimum, set line height to at least 1.5, keep lines between 45 and 75 characters, meet 4.5:1 contrast for normal text and 3:1 for large text, use a modular scale of 1.2 to 1.25 with no more than six steps, size everything in rem, scale fluidly with clamp(), limit yourself to one or two typefaces served as subset WOFF2 files, align text left, and build hierarchy with semantic headings plus white space rather than colour alone.
What is the minimum font size for accessibility?
WCAG does not name an absolute minimum pixel size, because it requires resizability instead: text must scale to 200% without loss of content or function. In practice, 16px is the accepted floor for body text since it matches the browser default and avoids Safari’s automatic zoom on form fields. Anything at 12px or below should be treated as a defect.
What are the five main rules of typography?
Condensed to five: (1) keep type large enough to read comfortably, (2) give lines enough leading, around 1.5 for body text, (3) control line length so the eye can return accurately, (4) guarantee contrast against the background, and (5) build a clear, consistent hierarchy. Everything else is refinement on top of those five.
What is the golden rule of typography?
Readability beats decoration. If a typographic choice makes the text harder to read, the choice loses. Type exists to deliver the message, and on screens the message has to survive different devices, zoom levels, ambient light and user stylesheets.
Is 14px too small for body text?
For dense interface elements such as table cells, badges or secondary metadata, 14px is acceptable. For content people read in sequence, such as articles, product descriptions or help documentation, it is too small: it forces zooming and increases reading effort. Reserve 14px for supporting roles.
Should headings and body text use different fonts?
Not necessarily. A single family with a strong weight range often produces cleaner, faster pages than a pairing. If you do pair, make the contrast obvious, for example a geometric or serif display face with a neutral sans for body, and check that the x-heights are compatible so the sizes feel intentional.
How do I test my typography for accessibility?
Check contrast with a contrast checker or your browser’s inspector, run an automated audit such as Lighthouse or axe, then do three manual passes: zoom text to 200%, narrow the viewport to 320px, and apply the WCAG 1.4.12 text spacing overrides with a bookmarklet or user stylesheet. Automated tools catch contrast; only the manual passes catch broken layouts.
Bringing it together
Good screen typography is not a matter of taste fighting with accessibility. The WCAG numbers describe the conditions under which text actually gets read, and every rule above turns one of those conditions into a value you can put in a design token. Set the tokens once, document them, and readability stops being something you argue about in review and becomes something your system produces by default.

