Typography and Readability (Font Size, Line Height)
1. Font Size (rem, em) and Zoom Support
| WCAG Requirement | Level | Specification | Implementation |
|---|---|---|---|
| Resize Text (1.4.4) | AA | Text can be resized up to 200% without loss of content or functionality | Use relative units (rem, em, %) |
| Reflow (1.4.10) | AA | Content reflows at 320px width without horizontal scrolling | Responsive design with media queries |
| Text Spacing (1.4.12) | AA | No loss of content when spacing is adjusted | Avoid fixed heights, use min-height |
| Minimum Base Size | Best practice | 16px minimum for body text | font-size: 16px or 1rem |
| Zoom Support | AA | Support browser zoom up to 200% | Don't disable user scaling |
Example: Zoom and resize support
<!-- Proper meta viewport (allows user zoom) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- NEVER disable zoom (accessibility violation) -->
<!-- BAD: -->
<meta name="viewport" content="user-scalable=no, maximum-scale=1.0">
<!-- Use relative units for font sizes -->
<style>
/* Base font size on html element */
html {
font-size: 16px; /* Base size for rem calculations */
}
/* Use rem for most text (scales with user preferences) */
body {
font-size: 1rem; /* 16px by default */
line-height: 1.5;
}
h1 {
font-size: 2.5rem; /* 40px at default */
}
h2 {
font-size: 2rem; /* 32px at default */
}
h3 {
font-size: 1.5rem; /* 24px at default */
}
p {
font-size: 1rem; /* 16px at default */
}
small {
font-size: 0.875rem; /* 14px at default */
}
/* Use em for component-relative sizing */
.button {
font-size: 1rem;
padding: 0.5em 1em; /* Scales with button font size */
}
.button--large {
font-size: 1.25rem;
padding: 0.5em 1em; /* Proportionally larger */
}
/* Responsive font sizing */
@media (max-width: 768px) {
html {
font-size: 14px; /* Smaller base on mobile */
}
}
@media (min-width: 1200px) {
html {
font-size: 18px; /* Larger base on large screens */
}
}
/* Fluid typography (clamps between min and max) */
h1 {
font-size: clamp(2rem, 5vw, 3rem);
}
/* Support text spacing adjustments (WCAG 1.4.12) */
* {
/* Don't set fixed heights that break with spacing changes */
/* Use min-height instead of height when possible */
}
.card {
min-height: 200px; /* Not height: 200px */
padding: 1rem;
}
/* Text should reflow without horizontal scroll at 320px */
.content {
max-width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
}
</style>
| Unit Type | Description | Use Case | Accessibility |
|---|---|---|---|
| rem | Relative to root font-size | Most text, spacing, layout | ✓ Respects user preferences |
| em | Relative to parent font-size | Component-scoped sizing, padding | ✓ Respects user preferences |
| % (percentage) | Relative to parent | Widths, responsive layouts | ✓ Flexible |
| px (pixels) | Absolute unit | Borders, shadows, small details | ⚠ Doesn't scale with zoom |
| vw/vh | Viewport percentage | Full-screen sections, fluid type | ⚠ Can cause reflow issues |
| clamp() | Min, preferred, max | Fluid typography with bounds | ✓ Responsive + bounded |
Warning: Never use
maximum-scale=1.0 or user-scalable=no in viewport
meta tag. This prevents users from zooming and fails WCAG 1.4.4.
2. Line Height (line-height: 1.5) and Spacing Guidelines
| Spacing Property | WCAG 1.4.12 Minimum | Recommended | CSS Property |
|---|---|---|---|
| Line Height | 1.5× font size (paragraphs) | 1.5-1.8 | line-height: 1.5 |
| Paragraph Spacing | 2× font size | 1.5-2rem | margin-bottom: 1.5rem |
| Letter Spacing | 0.12× font size | normal to 0.05em | letter-spacing: 0.02em |
| Word Spacing | 0.16× font size | normal to 0.1em | word-spacing: 0.05em |
Example: Accessible spacing implementation
<style>
/* Base typography with accessible spacing */
body {
font-size: 1rem;
line-height: 1.6; /* Exceeds 1.5 minimum */
letter-spacing: 0.01em;
}
/* Paragraph spacing */
p {
margin-bottom: 1.5rem; /* 2× font size minimum */
line-height: 1.6;
}
/* Heading spacing */
h1, h2, h3, h4, h5, h6 {
line-height: 1.3; /* Tighter for headings OK */
margin-top: 2rem;
margin-bottom: 1rem;
}
/* Lists with adequate spacing */
ul, ol {
margin-bottom: 1.5rem;
}
li {
margin-bottom: 0.5rem;
line-height: 1.6;
}
/* Dense text (tables, code) can be tighter */
table {
line-height: 1.4; /* Still readable */
}
code, pre {
line-height: 1.5;
letter-spacing: 0;
}
/* Support user text spacing overrides (WCAG 1.4.12) */
/* Users may apply: */
* {
/* line-height: 1.5 */
/* letter-spacing: 0.12em */
/* word-spacing: 0.16em */
/* paragraph spacing: 2em */
}
/* Ensure content doesn't break when spacing is increased */
.card {
min-height: fit-content; /* Not fixed height */
padding: 1rem;
}
.button {
padding: 0.75em 1.5em;
min-height: 2.5em; /* Not height: 40px */
}
/* Long-form content optimized for readability */
article p {
max-width: 65ch; /* 65-75 characters per line */
line-height: 1.7;
margin-bottom: 1.5rem;
}
/* Tight spacing exceptions (sparingly) */
.compact-list {
line-height: 1.4;
}
.compact-list li {
margin-bottom: 0.25rem;
}
</style>
<!-- Text spacing adjustment test -->
<style>
/* Apply maximum WCAG 1.4.12 spacing to test */
.wcag-spacing-test * {
line-height: 1.5 !important;
letter-spacing: 0.12em !important;
word-spacing: 0.16em !important;
}
.wcag-spacing-test p {
margin-bottom: 2em !important;
}
/* Content should remain readable and not clip */
</style>
| Content Type | Line Height | Max Line Length | Rationale |
|---|---|---|---|
| Body Text | 1.5-1.8 | 65-75 characters | Optimal reading comfort |
| Headings | 1.2-1.4 | 40-50 characters | Tighter for visual hierarchy |
| Captions | 1.4-1.6 | 55-65 characters | Small text needs more spacing |
| Code Blocks | 1.5-1.6 | 80-120 characters | Monospace allows longer lines |
| Tables | 1.4-1.5 | Varies by column | Denser spacing acceptable |
| UI Labels | 1.3-1.5 | N/A | Compact for interface elements |
Note: The 65-75 character line length guideline is based on readability research. Use
max-width: 65ch to implement this in CSS (1ch = width of the "0" character in the current font).
3. Text Scaling and Responsive Typography
| Technique | Method | Benefits | Considerations |
|---|---|---|---|
| Fluid Typography | clamp(min, preferred, max) |
Smooth scaling between breakpoints | Ensure minimum meets WCAG |
| Viewport-Based | font-size: calc(1rem + 1vw) |
Scales with viewport | Can be too small/large at extremes |
| Media Query Steps | Different sizes per breakpoint | Precise control at each size | Sudden jumps between breakpoints |
| Container Queries | Size based on container width | Component-level responsiveness | Newer browser support |
| User Preference | Respect browser font-size setting | User control, accessibility | Must use relative units |
Example: Responsive typography strategies
<style>
/* Strategy 1: Fluid typography with clamp() */
h1 {
font-size: clamp(1.75rem, 4vw + 1rem, 3.5rem);
/* Min: 28px, Scales with viewport, Max: 56px */
}
h2 {
font-size: clamp(1.5rem, 3vw + 0.5rem, 2.5rem);
}
p {
font-size: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);
}
/* Strategy 2: Media query steps */
body {
font-size: 16px;
}
@media (max-width: 480px) {
body { font-size: 14px; }
h1 { font-size: 1.75rem; }
}
@media (min-width: 481px) and (max-width: 768px) {
body { font-size: 15px; }
h1 { font-size: 2rem; }
}
@media (min-width: 769px) and (max-width: 1024px) {
body { font-size: 16px; }
h1 { font-size: 2.5rem; }
}
@media (min-width: 1025px) {
body { font-size: 18px; }
h1 { font-size: 3rem; }
}
/* Strategy 3: Viewport-based with bounds */
h1 {
font-size: calc(1.5rem + 2vw);
max-font-size: 4rem;
min-font-size: 2rem; /* Note: not a real property, use clamp */
}
/* Better: using clamp for the same effect */
h1 {
font-size: clamp(2rem, 1.5rem + 2vw, 4rem);
}
/* Strategy 4: Container queries (modern browsers) */
@container (min-width: 400px) {
.card h2 {
font-size: 1.5rem;
}
}
@container (min-width: 600px) {
.card h2 {
font-size: 2rem;
}
}
/* Strategy 5: Modular scale */
:root {
--ratio: 1.25; /* Major third */
--base-size: 1rem;
--text-xs: calc(var(--base-size) / var(--ratio) / var(--ratio));
--text-sm: calc(var(--base-size) / var(--ratio));
--text-base: var(--base-size);
--text-lg: calc(var(--base-size) * var(--ratio));
--text-xl: calc(var(--base-size) * var(--ratio) * var(--ratio));
--text-2xl: calc(var(--base-size) * var(--ratio) * var(--ratio) * var(--ratio));
}
small { font-size: var(--text-sm); }
body { font-size: var(--text-base); }
h3 { font-size: var(--text-lg); }
h2 { font-size: var(--text-xl); }
h1 { font-size: var(--text-2xl); }
/* Responsive adjustments to base size */
@media (max-width: 768px) {
:root {
--base-size: 0.875rem; /* 14px */
}
}
@media (min-width: 1200px) {
:root {
--base-size: 1.125rem; /* 18px */
}
}
/* Ensure minimum sizes at all breakpoints */
@media (max-width: 480px) {
body {
font-size: max(16px, 1rem); /* Never below 16px */
}
}
</style>
Example: Accessible responsive typography system
<style>
/* Complete responsive type system */
:root {
/* Base sizes */
--font-size-min: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-max: 1.125rem; /* 18px */
/* Fluid size between 320px and 1200px viewport */
--fluid-min-width: 320;
--fluid-max-width: 1200;
/* Calculate fluid type */
--fluid-bp: calc(
(100vw - var(--fluid-min-width) * 1px) /
(var(--fluid-max-width) - var(--fluid-min-width))
);
}
/* Fluid body text */
body {
font-size: calc(
var(--font-size-min) +
(var(--font-size-max) - var(--font-size-min)) * var(--fluid-bp)
);
line-height: 1.6;
}
/* Simplified with clamp() */
body {
font-size: clamp(
var(--font-size-min),
0.875rem + 0.25vw,
var(--font-size-max)
);
}
/* Heading scale */
h1 { font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem); }
h2 { font-size: clamp(1.75rem, 1.25rem + 1.5vw, 2.75rem); }
h3 { font-size: clamp(1.5rem, 1rem + 1vw, 2rem); }
h4 { font-size: clamp(1.25rem, 0.875rem + 0.75vw, 1.5rem); }
h5 { font-size: clamp(1.125rem, 0.875rem + 0.5vw, 1.25rem); }
h6 { font-size: clamp(1rem, 0.875rem + 0.25vw, 1.125rem); }
/* Ensure accessibility: never go below minimums */
@supports not (font-size: clamp(1rem, 1vw, 2rem)) {
/* Fallback for older browsers */
body { font-size: 16px; }
h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
/* etc... */
}
</style>
Warning: When using viewport units (vw, vh), always use
clamp() or
max() to ensure text doesn't become too small on narrow viewports, which would fail WCAG 1.4.4.
4. Reading Flow and Text Layout
| Layout Principle | Specification | Benefits | Implementation |
|---|---|---|---|
| Line Length | 50-75 characters optimal | Reduces eye fatigue, improves comprehension | max-width: 65ch |
| Text Alignment | Left-aligned for LTR languages | Consistent left edge aids tracking | text-align: left |
| Justified Text | Avoid or use with hyphenation | Prevents uneven spacing | hyphens: auto if justified |
| Column Width | Single column on mobile, multi on desktop | Appropriate line length at all sizes | columns or grid layout |
| Orphans/Widows | Prevent single lines at breaks | Visual continuity | orphans: 2; widows: 2 |
| Vertical Rhythm | Consistent spacing between elements | Visual harmony, scanability | Baseline grid or spacing scale |
Example: Optimal reading flow layout
<style>
/* Article layout optimized for readability */
article {
max-width: 65ch; /* 50-75 character line length */
margin: 0 auto;
padding: 2rem 1rem;
}
/* Long-form text styling */
article p {
text-align: left; /* Never center long text */
line-height: 1.7;
margin-bottom: 1.5rem;
/* Prevent orphans and widows */
orphans: 2;
widows: 2;
/* Better word breaking */
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto; /* Enable hyphenation */
}
/* Avoid justified text or improve it */
.justified {
text-align: justify;
hyphens: auto; /* Required for even spacing */
text-justify: inter-word; /* Better spacing algorithm */
}
/* Multi-column layout (use carefully) */
@media (min-width: 900px) {
.multi-column {
column-count: 2;
column-gap: 3rem;
column-rule: 1px solid #e0e0e0;
}
/* Prevent headings from breaking across columns */
.multi-column h2,
.multi-column h3 {
break-after: avoid;
column-span: all; /* Heading spans all columns */
}
/* Prevent images from breaking */
.multi-column img {
break-inside: avoid;
}
}
/* Responsive line length */
.content {
width: 100%;
max-width: 65ch;
}
@media (max-width: 768px) {
.content {
max-width: 100%; /* Full width on mobile */
padding: 0 1rem;
}
}
/* Vertical rhythm system */
:root {
--line-height: 1.6;
--rhythm: calc(1rem * var(--line-height));
}
h1, h2, h3, p, ul, ol {
margin-bottom: var(--rhythm);
}
/* Lists with proper spacing */
ul, ol {
padding-left: 1.5rem;
}
li {
margin-bottom: 0.5rem;
line-height: 1.6;
}
/* Pull quotes or callouts */
.callout {
max-width: 45ch; /* Shorter for emphasis */
margin: 2rem auto;
padding: 1.5rem;
font-size: 1.125rem;
border-left: 4px solid #0066cc;
}
/* Code blocks with scrolling instead of wrapping */
pre {
max-width: 100%;
overflow-x: auto;
padding: 1rem;
line-height: 1.5;
}
/* Prevent very long words from breaking layout */
.user-content {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word; /* Breaks long words */
}
/* Direction support for RTL languages */
[dir="rtl"] {
text-align: right;
}
[dir="rtl"] ul,
[dir="rtl"] ol {
padding-left: 0;
padding-right: 1.5rem;
}
</style>
<article lang="en">
<h1>Article Title</h1>
<p>
This paragraph has an optimal line length of around 65 characters,
which research shows provides the best reading experience. The text
is left-aligned with adequate line height for easy tracking.
</p>
<p>
Long form content should never be centered or right-aligned as this
makes it difficult for readers to find the start of each new line.
</p>
</article>
Note: Centered or right-aligned text is acceptable for short content like headings, captions,
or UI elements, but long-form body text should always be left-aligned (or right-aligned for RTL languages).
5. Font Selection (Dyslexia-Friendly Fonts) for Accessibility
| Font Characteristic | Accessible Choice | Avoid | Why |
|---|---|---|---|
| Letter Differentiation | Clear distinction between I, l, 1, O, 0 | Fonts where letters look identical | Reduces confusion for dyslexic users |
| X-Height | Larger x-height (relative to cap height) | Very small x-height | Improves readability at small sizes |
| Weight Variation | Multiple weights available | Single weight only | Needed for hierarchy and emphasis |
| Character Spacing | Slightly open spacing | Very tight or condensed | Easier to distinguish letters |
| Decorative Features | Minimal for body text | Excessive serifs, swashes, ornaments | Can distract or confuse readers |
| Font Style | Sans-serif for UI, serif OK for long text | Script, handwriting, novelty fonts | Novelty fonts harder to read |
Example: Accessible font selection
<style>
/* Accessible font stacks */
/* System font stack (no web fonts needed, fast) */
body {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
sans-serif;
}
/* Readable serif stack for long-form content */
article {
font-family:
"Iowan Old Style",
"Palatino Linotype",
"URW Palladio L",
Georgia,
serif;
}
/* Monospace stack for code */
code, pre {
font-family:
"SF Mono",
Monaco,
"Cascadia Code",
"Roboto Mono",
Consolas,
"Courier New",
monospace;
}
/* Web fonts with careful selection */
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* Show fallback during load */
font-weight: 400;
font-style: normal;
}
/* Use variable fonts for performance */
@font-face {
font-family: 'InterVariable';
src: url('inter-variable.woff2') format('woff2');
font-weight: 100 900; /* Supports all weights */
font-display: swap;
}
/* Accessible fonts recommended: */
/* - Inter (excellent readability) */
/* - Atkinson Hyperlegible (designed for low vision) */
/* - Open Sans, Roboto (good alternatives) */
/* - Lexend (dyslexia-friendly) */
/* Font adjustments for accessibility */
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* Never use ultra-thin weights for body text */
.thin-text {
font-weight: 100; /* BAD for accessibility */
}
.readable-text {
font-weight: 400; /* GOOD: normal weight */
}
/* Italic text - use sparingly */
em, i {
font-style: italic;
/* Some fonts have poor italics - consider checking */
}
/* All caps - harder to read, use sparingly */
.uppercase {
text-transform: uppercase;
letter-spacing: 0.05em; /* Increase spacing */
font-size: 0.875em; /* Slightly smaller often better */
}
/* Font size adjustments */
small {
font-size: 0.875rem; /* Don't go below 14px equivalent */
}
/* Ensure minimum legible sizes */
@media (max-width: 768px) {
small {
font-size: max(0.875rem, 14px);
}
}
</style>
<!-- Example: Dyslexia-friendly font -->
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;600&display=swap" rel="stylesheet">
<style>
.dyslexia-friendly {
font-family: 'Lexend', sans-serif;
font-size: 1.125rem; /* Slightly larger */
line-height: 1.8; /* More spacing */
letter-spacing: 0.05em;
word-spacing: 0.1em;
}
</style>
| Font Family | Type | Accessibility Features | Best Use |
|---|---|---|---|
| Atkinson Hyperlegible | Sans-serif | Designed for low vision, high differentiation | UI, body text, low vision users |
| Inter | Sans-serif | Tall x-height, open apertures | UI, screens, body text |
| Lexend | Sans-serif | Designed for dyslexia | Body text, educational content |
| Open Dyslexic | Sans-serif | Weighted bottoms, unique shapes | Dyslexic users (optional) |
| Verdana | Sans-serif | Large x-height, wide spacing | Small sizes, screens |
| Georgia | Serif | Large characters, screen-optimized | Long-form reading |
| Comic Sans | Sans-serif | Letter differentiation (controversial) | Dyslexic users (user preference) |
Warning: Avoid using font weights below 400 for body text, and
never use decorative or script fonts for important content. They can be difficult or impossible for some users
to read.
Note: Consider providing a font switcher for users with dyslexia or low vision. Allow users to
choose between standard fonts and specialized alternatives like Lexend or OpenDyslexic.
Typography and Readability Quick Reference
- Use relative units (rem, em) for all font sizes - never disable zoom with viewport meta
- Minimum 16px base font size; text must be resizable to 200% without loss of content
- Line height minimum 1.5 for body text; paragraph spacing minimum 2× font size (WCAG 1.4.12)
- Optimal line length: 50-75 characters (use
max-width: 65ch) - Left-align body text (right-align for RTL); avoid justified text unless using hyphenation
- Use
clamp()for fluid typography with minimum/maximum bounds - Choose fonts with clear letter differentiation (I/l/1, O/0) and adequate x-height
- Provide font weight variation (400 minimum for body text; avoid ultra-thin weights)
- Test spacing with WCAG 1.4.12 adjustments: content must not clip or overlap
- Consider specialized fonts (Atkinson Hyperlegible, Lexend) for improved accessibility