Skip to content

Accessible Typography

Typography is the foundation of readable interfaces. Get it wrong, and nothing else matters—users can’t read your content. Research shows accessible typography can reduce cognitive strain by up to 35% for users with visual impairments and reduce reading fatigue by 25% for all users.

This page provides exact specifications that AI agents and designers can apply directly. All values are WCAG 2.2 compliant unless noted otherwise.

ContextMinimumRecommendedMaximumUnit
Body text16px18px24pxrem preferred
Secondary text (captions)12px14px16pxrem preferred
Small print (legal)12px12px14pxrem preferred
Large text (headings)24pxrem preferred
Large text (bold)18.5pxrem preferred

Critical rule: Never use font sizes below 12px for any readable text.

Content TypeMinimumRecommendedWCAG Reference
Body text (paragraphs)1.5 (150%)1.61.4.12
Headings1.11.2-1.3
UI labels1.21.4
Long-form reading1.51.751.4.12

Users must be able to override these values without loss of content:

PropertyMinimum OverrideCSS Property
Line height1.5× font sizeline-height
Paragraph spacing2× font sizemargin-bottom
Letter spacing0.12× font sizeletter-spacing
Word spacing0.16× font sizeword-spacing
Text TypeWCAG AAWCAG AAANotes
Normal text (<24px)4.5:17:1Most body text
Large text (≥24px)3:14.5:1Headlines
Large bold text (≥18.5px bold)3:14.5:1Bold subheadings
UI components3:1Buttons, inputs, icons
Placeholder text4.5:17:1Often fails—check this
Disabled textNo requirementBut 3:1 recommended
ContextMinimumOptimalMaximum
Body text4565-7590
Mobile3545-6075
Dyslexic readers3550-6070

Research finding: Shorter lines improve reading speed by 27% for dyslexic readers.

rules:
- id: typography-body-size
severity: error
check: "Body text font-size >= 16px (1rem)"
selector: "p, li, td, dd, .body-text"
- id: typography-minimum-size
severity: error
check: "All readable text >= 12px"
selector: "*:not(sup):not(sub)"
- id: typography-line-height
severity: warning
check: "Body text line-height >= 1.5"
selector: "p, li, .body-text"
- id: typography-contrast-normal
severity: error
check: "Normal text contrast ratio >= 4.5:1"
wcag: "1.4.3 AA"
- id: typography-contrast-large
severity: error
check: "Large text (>=24px or >=18.5px bold) contrast >= 3:1"
wcag: "1.4.3 AA"
- id: typography-line-length
severity: warning
check: "Line length <= 80 characters"
selector: "p, li"
- id: typography-text-resize
severity: error
check: "Text resizable to 200% without loss of content"
wcag: "1.4.4 AA"
- id: typography-relative-units
severity: warning
check: "Font sizes use relative units (rem, em)"
selector: "[style*='font-size']"
:root {
/* Font sizes */
--font-size-xs: 0.75rem; /* 12px - minimum readable */
--font-size-sm: 0.875rem; /* 14px - secondary text */
--font-size-base: 1rem; /* 16px - body text */
--font-size-lg: 1.125rem; /* 18px - emphasized body */
--font-size-xl: 1.25rem; /* 20px - small headings */
--font-size-2xl: 1.5rem; /* 24px - h4, large text threshold */
--font-size-3xl: 1.875rem; /* 30px - h3 */
--font-size-4xl: 2.25rem; /* 36px - h2 */
--font-size-5xl: 3rem; /* 48px - h1 */
/* Line heights */
--line-height-tight: 1.25; /* headings */
--line-height-snug: 1.375; /* subheadings */
--line-height-normal: 1.5; /* body minimum */
--line-height-relaxed: 1.625; /* body recommended */
--line-height-loose: 1.75; /* long-form reading */
/* Letter spacing */
--letter-spacing-tight: -0.025em; /* large headings */
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.025em; /* all caps, small text */
--letter-spacing-wider: 0.05em; /* uppercase labels */
/* Paragraph spacing */
--paragraph-spacing: 1.5em; /* 1.5× font size */
/* Line length */
--measure-narrow: 45ch;
--measure-base: 65ch;
--measure-wide: 80ch;
}
{
"typography": {
"fontSize": {
"xs": { "value": "0.75rem", "px": 12, "minReadable": true },
"sm": { "value": "0.875rem", "px": 14, "use": "secondary" },
"base": { "value": "1rem", "px": 16, "use": "body" },
"lg": { "value": "1.125rem", "px": 18, "use": "emphasized" },
"xl": { "value": "1.25rem", "px": 20, "use": "small-heading" },
"2xl": { "value": "1.5rem", "px": 24, "use": "heading", "wcagLargeText": true },
"3xl": { "value": "1.875rem", "px": 30, "use": "heading" },
"4xl": { "value": "2.25rem", "px": 36, "use": "heading" },
"5xl": { "value": "3rem", "px": 48, "use": "display" }
},
"lineHeight": {
"tight": { "value": 1.25, "use": "headings" },
"snug": { "value": 1.375, "use": "subheadings" },
"normal": { "value": 1.5, "use": "body-minimum", "wcagMinimum": true },
"relaxed": { "value": 1.625, "use": "body-recommended" },
"loose": { "value": 1.75, "use": "long-form" }
},
"measure": {
"narrow": { "value": "45ch", "characters": 45 },
"base": { "value": "65ch", "characters": 65, "optimal": true },
"wide": { "value": "80ch", "characters": 80, "maximum": true }
},
"contrast": {
"normalText": { "minimum": 4.5, "wcag": "AA" },
"largeText": { "minimum": 3.0, "wcag": "AA" },
"normalTextAAA": { "minimum": 7.0, "wcag": "AAA" },
"largeTextAAA": { "minimum": 4.5, "wcag": "AAA" }
}
}
}
font-size: clamp(MIN, PREFERRED, MAX)

Where PREFERRED typically combines viewport and relative units:

/* Accessible fluid typography pattern */
font-size: clamp(1rem, 0.5rem + 1vw, 1.5rem);

Critical: Always include a rem component in the preferred value. Using vw alone breaks zoom accessibility.

:root {
/* Fluid scale: scales between 320px and 1200px viewport */
--fluid-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--fluid-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
--fluid-base: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
--fluid-lg: clamp(1.125rem, 1rem + 0.6vw, 1.5rem);
--fluid-xl: clamp(1.25rem, 1rem + 1vw, 2rem);
--fluid-2xl: clamp(1.5rem, 1.1rem + 1.5vw, 2.5rem);
--fluid-3xl: clamp(1.875rem, 1.2rem + 2.5vw, 3.5rem);
--fluid-4xl: clamp(2.25rem, 1.5rem + 3vw, 4.5rem);
--fluid-5xl: clamp(3rem, 2rem + 4vw, 6rem);
}

Line height should also be fluid—tighter at larger sizes:

:root {
--fluid-leading-heading: clamp(1.1, 1.05 + 0.15vw, 1.3);
--fluid-leading-body: clamp(1.5, 1.4 + 0.1vw, 1.75);
}
h1 { line-height: var(--fluid-leading-heading); }
p { line-height: var(--fluid-leading-body); }

Fonts must have:

FeatureRequirementWhy
Character distinctionI, l, 1 visually distinctPrevents misreading
High x-height≥70% of cap heightImproves small-size readability
Open countersClear space in e, a, oAids character recognition
Consistent stroke widthUniform or near-uniformReduces visual complexity
Complete character setLatin Extended + common symbolsSupports internationalization
/* Modern system font stack - instant loading, optimized per platform */
font-family:
-apple-system, /* Safari Mac/iOS */
BlinkMacSystemFont, /* Chrome Mac */
"Segoe UI", /* Windows */
Roboto, /* Android, Chrome OS */
"Helvetica Neue", /* Older Mac */
Arial, /* Fallback */
"Noto Sans", /* Linux, CJK support */
sans-serif, /* Generic fallback */
"Apple Color Emoji", /* Mac emoji */
"Segoe UI Emoji", /* Windows emoji */
"Noto Color Emoji"; /* Linux/Android emoji */
font-family:
ui-monospace,
"Cascadia Code",
"Source Code Pro",
Menlo,
Consolas,
"DejaVu Sans Mono",
monospace;
FontTypex-heightCharacter ClarityNotes
InterSansHighExcellentFree, variable
Source Sans 3SansHighExcellentAdobe, free
Atkinson HyperlegibleSansVery HighDesigned for low visionFree
LexendSansHighGoodDesigned for reading
IBM Plex SansSansHighExcellentFree, variable
LiterataSerifHighGoodGoogle Docs default

Research shows font choice is less important than size and spacing for dyslexic readers:

What actually helps:

  • Font size ≥16px (larger preferred)
  • Generous line spacing (1.5-2.0)
  • Shorter line lengths (50-60 characters)
  • Left-aligned text (not justified)
  • Sans-serif fonts (simpler letter shapes)
  • Adequate letter and word spacing

Specialized fonts (OpenDyslexic, Dyslexie):

  • Research shows no significant improvement over Arial/Verdana
  • Some users report personal preference
  • Offer as an option, not default
IF context = "body-text":
font-size >= 16px (1rem)
line-height >= 1.5
IF context = "secondary-text" (captions, timestamps):
font-size >= 14px (0.875rem)
font-size < body-text-size
IF context = "heading":
IF level = 1: font-size >= 32px (2rem)
IF level = 2: font-size >= 24px (1.5rem)
IF level = 3: font-size >= 20px (1.25rem)
IF level = 4: font-size >= 18px (1.125rem)
line-height = 1.2-1.3
IF context = "legal-small-print":
font-size >= 12px (0.75rem)
WARN: Consider 14px for better accessibility
IF user-has-zoom-preference:
USE relative units (rem) only
ALLOW 200% zoom without overflow
IF font-size < 24px AND font-weight < 700:
contrast-ratio >= 4.5:1 (WCAG AA)
IF font-size >= 24px OR (font-size >= 18.5px AND font-weight >= 700):
contrast-ratio >= 3:1 (WCAG AA)
IF target = "AAA":
normal-text: contrast-ratio >= 7:1
large-text: contrast-ratio >= 4.5:1
IF element = "placeholder":
contrast-ratio >= 4.5:1
NOTE: Many placeholders fail this—verify
IF element = "disabled":
No WCAG requirement
RECOMMEND: contrast-ratio >= 3:1 for visual clarity
IF container-width / font-size > 80:
SET max-width on text container
RECOMMEND: max-width = 65ch
IF mobile-viewport:
max-width = min(100%, 75ch)
CHECK: text doesn't require horizontal scroll
<p style="
font-size: 1rem;
line-height: 1.6;
max-width: 65ch;
letter-spacing: normal;
">
Body text content here.
</p>
<h1 style="font-size: 2.5rem; line-height: 1.2; letter-spacing: -0.025em;">
Page Title
</h1>
<h2 style="font-size: 1.75rem; line-height: 1.25;">Section Heading</h2>
<h3 style="font-size: 1.25rem; line-height: 1.3;">Subsection</h3>
.prose {
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
line-height: 1.6;
max-width: 65ch;
margin-inline: auto;
padding-inline: 1rem;
}
.prose h1 {
font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem);
line-height: 1.2;
margin-block: 1em 0.5em;
}
.prose h2 {
font-size: clamp(1.5rem, 1.25rem + 1vw, 2.25rem);
line-height: 1.25;
margin-block: 1.5em 0.5em;
}
.prose p {
margin-block: 1em;
}
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
.prose { font-size: 1rem; } /* No fluid scaling */
}
/* Apply to test WCAG 1.4.12 compliance */
.text-spacing-test * {
line-height: 1.5 !important;
letter-spacing: 0.12em !important;
word-spacing: 0.16em !important;
}
.text-spacing-test p {
margin-bottom: 2em !important;
}
  • Body text size: ≥16px (1rem)
  • Minimum size: No text below 12px
  • Line height: Body text ≥1.5
  • Contrast normal: All normal text ≥4.5:1
  • Contrast large: Large text ≥3:1
  • Line length: ≤80 characters (65ch recommended)
  • Relative units: rem/em used for font-size
  • 200% zoom: No overflow or overlap at 200%
  • Text spacing: No content loss when spacing overridden (1.4.12)
  • System fonts or tested web fonts: Fonts verified for accessibility
1. Check all font-size values >= 12px minimum
2. Check body text >= 16px
3. Run contrast checker on all text/background combinations
4. Test at 200% browser zoom
5. Apply text spacing override styles, verify no content loss
6. Verify line length on widest viewport
7. Test with screen reader for content access

According to 2024 accessibility research, accessible typography can reduce cognitive strain by up to 35% for users with visual impairments. Smashing Magazine (2024) reports good spacing can reduce reading fatigue by 25%.

University research shows increasing line spacing from 100% to 120% improves reading accuracy by up to 20% and reduces eye strain by 30% during prolonged reading.

Studies comparing OpenDyslexic with standard fonts found no significant improvement in reading speed or accuracy. Research indicates that font size and spacing matter more than specialized typefaces for dyslexic readers. Shorter line lengths can improve reading speed by 27% for this population.

CSS clamp() is now supported by 91.4% of browsers, making fluid typography a standard practice. However, accessibility testing emphasizes that viewport-only units break zoom—always combine vw with rem.

Section508.gov guidance confirms WCAG 1.4.12 requires no loss of content when users override text spacing to 1.5× line height, 2× paragraph spacing, 0.12× letter spacing, and 0.16× word spacing.

Official Standards:

Type Scale Tools:

Research & Guidelines:

Font Resources: