Typography & Fonts: The Ultimate Guide
Text is the main interface of the web. Users read navigation labels, headings, forms, lessons, error messages, buttons, captions, and code examples. Typography is not just picking a pretty font. It is the design of reading.
Good typography improves:
- readability,
- hierarchy,
- accessibility,
- brand voice,
- scanning speed,
- and page performance.
1. The Typography Stack
CSS controls typography through several related properties:
body {
font-family: system-ui, sans-serif;
font-size: 1rem;
line-height: 1.6;
color: #0f172a;
}
The most important properties are:
font-family: which font is used.font-size: how large the text is.font-weight: how thick the text is.line-height: vertical rhythm.letter-spacing: spacing between letters.text-transform: uppercase, lowercase, capitalization.text-align: horizontal alignment.color: text color.
2. Font Families and Font Stacks
A font stack is a prioritized list. The browser tries the first font. If it is unavailable, it tries the next.
body {
font-family: "Source Sans 3", "Segoe UI", Arial, sans-serif;
}
Why stacks matter:
- Users have different operating systems.
- A web font might fail to load.
- Some characters may not exist in the primary font.
- Generic fallbacks keep text readable.
Generic Font Families
| Family | Use case | Examples |
|---|---|---|
serif | Editorial, formal, long-form reading | Georgia, Times |
sans-serif | Interfaces and dashboards | Arial, Helvetica |
monospace | Code, terminals, tabular data | Menlo, Consolas |
cursive | Handwritten tone | Brush-like fonts |
fantasy | Decorative display | Novelty fonts |
system-ui | Native operating system UI font | San Francisco, Segoe UI |
code {
font-family: "Fira Code", "SFMono-Regular", Consolas, monospace;
}
3. Web Fonts and @font-face
Web fonts are font files downloaded by the browser.
@font-face {
font-family: "Lesson Sans";
src: url("/fonts/lesson-sans.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
Key parts:
font-family: the name you will use in CSS.src: file location and format.font-weight: supported weight range.font-style: normal, italic, or oblique.font-display: loading behavior.
Use woff2 when possible. It is compressed and widely supported.
font-display
@font-face {
font-family: "Brand";
src: url("/brand.woff2") format("woff2");
font-display: swap;
}
font-display: swap tells the browser to show fallback text immediately, then swap in the web font when it loads. This avoids invisible text.
4. Font Loading Performance
Fonts can slow down pages. Treat them like important assets.
Practical guidelines:
- Use fewer font families.
- Use fewer weights.
- Prefer
woff2. - Use
font-display: swap. - Avoid importing fonts inside large CSS files when a framework has a better font-loading system.
- Test the page on a slow connection.
Bad:
body {
font-family: "HugeFontWithManyWeights", sans-serif;
}
Better:
body {
font-family: "FocusedFont", system-ui, sans-serif;
}
Load only the weights you actually use.
5. Font Size Units
CSS supports several sizing units. Choose units based on behavior.
Pixels
.caption {
font-size: 14px;
}
Pixels are precise, but they can be less flexible for user preferences.
rem
rem is relative to the root element, usually html.
body {
font-size: 1rem;
}
h1 {
font-size: 2.5rem;
}
If the root size is 16px, then:
1rem = 16px
2rem = 32px
0.875rem = 14px
rem is a good default for font sizes because it respects user scaling.
em
em is relative to the current element's font size.
button {
font-size: 1rem;
padding: 0.75em 1em;
}
This makes button padding scale with the button's text size.
6. Responsive Type with clamp()
clamp() lets text scale between a minimum and maximum.
h1 {
font-size: clamp(2rem, 5vw, 4.5rem);
}
Meaning:
minimum: 2rem
preferred: 5vw
maximum: 4.5rem
This is useful for hero headings that should grow on large screens but stay readable on mobile.
.hero-title {
font-size: clamp(2.25rem, 7vw, 6rem);
line-height: 0.95;
}
7. Line Height
line-height controls the vertical distance between lines.
p {
line-height: 1.65;
}
Use unitless values for most text. A unitless value scales with the element's font size.
General guidance:
- Headings:
1to1.2 - Body text:
1.5to1.75 - Captions:
1.35to1.5 - Buttons and nav labels:
1to1.25
8. Measure: Line Length
Line length is called measure. If lines are too long, reading becomes tiring. If lines are too short, the eye has to jump too often.
Use max-width or max-inline-size for reading content:
.article-body {
max-inline-size: 70ch;
line-height: 1.7;
}
ch is based on the width of the 0 character in the current font. For body copy, 60ch to 75ch is often comfortable.
9. Font Weight and Style
h1 {
font-weight: 800;
}
em {
font-style: italic;
}
Common weights:
100: thin300: light400: regular500: medium600: semibold700: bold800: extrabold900: black
Use weight to create hierarchy, but do not rely on weight alone. Combine size, spacing, color, and placement.
10. Letter Spacing and Word Spacing
letter-spacing adjusts spacing between characters.
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.16em;
}
This works well for small uppercase labels.
Avoid large positive letter spacing on normal paragraph text because it can hurt readability.
word-spacing adjusts spacing between words:
.wide-words {
word-spacing: 0.2em;
}
Use it rarely.
11. Text Transform and Alignment
.label {
text-transform: uppercase;
}
.headline {
text-align: center;
}
Common values:
text-transform: uppercasetext-transform: lowercasetext-transform: capitalizetext-align: lefttext-align: centertext-align: righttext-align: justify
Use justify carefully. It can create awkward rivers of whitespace, especially on narrow screens.
12. Text Decoration
Links need clear visual affordance.
a {
color: #1d4ed8;
text-decoration-line: underline;
text-decoration-thickness: 0.08em;
text-underline-offset: 0.18em;
}
Shorthand:
a {
text-decoration: underline wavy #1d4ed8;
}
For accessibility, do not identify links by color alone in long text. Underlines help users recognize clickable text.
13. Text Overflow and Wrapping
Single-line truncation:
.breadcrumb-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Long words and URLs:
.article {
overflow-wrap: anywhere;
}
Preserving whitespace:
pre {
white-space: pre-wrap;
}
Useful white-space values:
normal: collapses spaces and wraps.nowrap: prevents wrapping.pre: preserves spaces and line breaks.pre-wrap: preserves spaces and wraps.
14. Typographic Hierarchy
Hierarchy helps users scan a page.
.page-title {
font-size: clamp(2.5rem, 8vw, 5rem);
line-height: 0.95;
font-weight: 900;
}
.section-title {
font-size: 1.5rem;
line-height: 1.2;
font-weight: 800;
}
.body-copy {
font-size: 1rem;
line-height: 1.7;
}
Use contrast between levels. A heading that is only slightly larger than body text will not feel like a heading.
15. Variable Fonts
Variable fonts store multiple styles in one file. Instead of downloading separate files for regular, bold, condensed, and expanded versions, a variable font can expose adjustable axes.
.dynamic-text {
font-family: "Roboto Flex", sans-serif;
font-variation-settings: "wght" 542, "wdth" 100;
}
Common axes:
wght: weightwdth: widthslnt: slantopsz: optical size
Example:
.dynamic-text:hover {
font-variation-settings: "wght" 850, "wdth" 115;
}
Use variable fonts when you need flexibility and can control performance.
16. Rendering and Smoothing
Some projects use font smoothing:
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
This can make text appear lighter on some systems. Use it carefully and test on different displays.
text-rendering can enable extra typographic features:
body {
text-rendering: optimizeLegibility;
}
It may improve ligatures and kerning in some browsers, but it is not a magic performance-free improvement.
17. Accessible Typography
Readable typography is accessibility work.
Practical rules:
- Keep body text at a comfortable size, usually
1remor larger. - Use strong color contrast.
- Avoid long lines.
- Do not use all-caps for long passages.
- Do not put important text inside images.
- Respect user zoom.
- Avoid fixed-height containers that clip text when users increase font size.
- Make focus states readable and visible.
Bad:
.body {
font-size: 12px;
line-height: 1.1;
color: #94a3b8;
}
Better:
.body {
font-size: 1rem;
line-height: 1.7;
color: #1f2937;
}
18. Practical Typography Base
:root {
--font-body: "Source Sans 3", system-ui, sans-serif;
--font-heading: "Fraunces", Georgia, serif;
--font-code: "Fira Code", "SFMono-Regular", Consolas, monospace;
}
body {
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.65;
color: #1f2937;
background: #fffdf8;
-webkit-font-smoothing: antialiased;
}
h1,
h2,
h3 {
font-family: var(--font-heading);
line-height: 1.1;
color: #0f172a;
}
h1 {
font-size: clamp(2.5rem, 8vw, 5.5rem);
letter-spacing: -0.04em;
}
p {
max-width: 70ch;
}
code,
pre {
font-family: var(--font-code);
}
This creates a readable base with separate roles for body text, headings, and code.
19. Practical Article Layout
.article {
max-inline-size: 72ch;
margin-inline: auto;
padding: 2rem 1rem;
}
.article h1 {
margin-block-end: 1rem;
}
.article p {
margin-block: 0;
}
.article p + p {
margin-block-start: 1rem;
}
.article blockquote {
margin: 2rem 0;
padding-inline-start: 1.25rem;
border-inline-start: 4px solid #2563eb;
font-size: 1.25rem;
line-height: 1.6;
font-style: italic;
}
This pattern controls measure, vertical spacing, and quote styling.
20. Debugging Checklist
When text looks wrong, check:
- Which
font-familyactually loaded? - Is the fallback font being used?
- Is the font weight available?
- Is the text too small?
- Is line height too tight?
- Is the line length too wide?
- Is color contrast sufficient?
- Is
letter-spacinghurting readability? - Is a parent changing
font-sizethroughem? - Is text clipped by a fixed height?
21. Mini Practice
Create a readable article style with:
- a body font stack,
- a heading font stack,
- comfortable line height,
- responsive heading size,
- readable paragraph width,
- and styled inline code.
Possible answer:
.lesson {
font-family: "Source Sans 3", system-ui, sans-serif;
color: #1f2937;
line-height: 1.7;
}
.lesson h1 {
font-family: Georgia, serif;
font-size: clamp(2.25rem, 6vw, 4.5rem);
line-height: 1;
color: #0f172a;
}
.lesson p {
max-width: 70ch;
}
.lesson code {
font-family: "SFMono-Regular", Consolas, monospace;
background: #f1f5f9;
border-radius: 0.375rem;
padding: 0.1em 0.35em;
}