Typography and Text Styling
Fonts, sizing, spacing, and readable text
CSS typography controls everything about how text looks and feels. Good typographic choices are the foundation of readable, professional interfaces — and CSS gives you fine-grained control over fonts, spacing, sizing, and rendering.
Loading Custom Fonts with @font-face
@font-face {
font-family: 'Inter';
src:
url('/fonts/Inter.woff2') format('woff2'),
url('/fonts/Inter.woff') format('woff');
font-weight: 100 900; /* variable font weight range */
font-style: normal;
font-display: swap; /* show fallback immediately; swap when loaded */
}
font-display: swap prevents invisible text during load (FOIT). optional is even more conservative — it never causes a layout shift but may not show the custom font on slow connections.
Variable Fonts
A variable font encodes a whole family in a single file with axes you can animate smoothly in CSS — weight, width, slant, optical size, and custom axes.
body { font-family: 'Inter', system-ui, sans-serif; }
.heading { font-variation-settings: 'wght' 700, 'slnt' -5; }
/* Animate weight on hover */
.link {
font-weight: 400;
transition: font-weight 0.2s ease;
}
.link:hover { font-weight: 600; }
Fluid Typography with clamp()
clamp(min, preferred, max) creates type that scales fluidly with the viewport — no media query breakpoints needed for font sizes.
/* Scales from 1rem at 320px to 1.5rem at 1200px */
h1 { font-size: clamp(1.75rem, 4vw + 0.5rem, 3rem); }
p { font-size: clamp(1rem, 1vw + 0.75rem, 1.25rem); }
/* Line height should scale with font size */
body { line-height: clamp(1.4, 1.2 + 0.5vw, 1.7); }
The Core Text Properties
.article {
font-size: 1.125rem; /* ~18px — more readable than 16px for long text */
line-height: 1.7; /* 1.5–1.8 for body text */
font-weight: 400;
letter-spacing: -0.01em; /* slightly tighter for large headings */
word-spacing: 0.05em;
max-width: 65ch; /* ch unit = width of '0', ideal for line length */
text-wrap: pretty; /* Chrome 117+ — avoids orphaned last words */
}
.heading {
letter-spacing: -0.03em;
text-wrap: balance; /* balances line breaks in headings */
}
Responsive Text Truncation
/* Single-line truncation */
.title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Multi-line clamp (WebKit, now widely supported) */
.excerpt {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
Font Stack Best Practices
/* System font stack — no network request, native feel */
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, Helvetica, Arial, sans-serif;
}
/* Monospace for code */
code, pre {
font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code',
'Source Code Pro', Menlo, monospace;
font-feature-settings: 'liga' 1, 'calt' 1; /* enable ligatures */
}
OpenType Features
.price {
font-variant-numeric: tabular-nums; /* columns align correctly */
font-feature-settings: 'tnum' 1;
}
.small-caps {
font-variant-caps: small-caps; /* true small caps, not scaled */
}
.ordinal {
font-variant-numeric: ordinal; /* 1st, 2nd, 3rd */
}
Best Practices
- Limit custom font weights to those you actually use — each is a separate network request
- Preload critical fonts:
<link rel="preload" as="font" crossorigin> - Use
remfor font sizes so users can scale with browser zoom - Target 45–75 characters per line (
max-width: 65ch) for optimal readability - Ensure body text colour meets WCAG AA contrast (4.5:1) against the background