Typography & Fonts: The Ultimate Guide

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.
Readable hierarchyfont family, size, weight, line height, spacing, and color work together.Typography is a system, not a single property.

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

FamilyUse caseExamples
serifEditorial, formal, long-form readingGeorgia, Times
sans-serifInterfaces and dashboardsArial, Helvetica
monospaceCode, terminals, tabular dataMenlo, Consolas
cursiveHandwritten toneBrush-like fonts
fantasyDecorative displayNovelty fonts
system-uiNative operating system UI fontSan 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.

1rem = root size2rem scales1em = current sizepadding scalesUse rem for type scaleUse em for component spacing

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: 1 to 1.2
  • Body text: 1.5 to 1.75
  • Captions: 1.35 to 1.5
  • Buttons and nav labels: 1 to 1.25
Too tight line heightmakes long passagesharder to read.Comfortable line heighthelps readers movefrom line to line.line-height: 1.1line-height: 1.7

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: thin
  • 300: light
  • 400: regular
  • 500: medium
  • 600: semibold
  • 700: bold
  • 800: extrabold
  • 900: 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: uppercase
  • text-transform: lowercase
  • text-transform: capitalize
  • text-align: left
  • text-align: center
  • text-align: right
  • text-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;
}
Main headingSection headingBody text explains the topic with comfortable spacing and readable contrast.Caption text supports the main content.

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: weight
  • wdth: width
  • slnt: slant
  • opsz: 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 1rem or 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:

  1. Which font-family actually loaded?
  2. Is the fallback font being used?
  3. Is the font weight available?
  4. Is the text too small?
  5. Is line height too tight?
  6. Is the line length too wide?
  7. Is color contrast sufficient?
  8. Is letter-spacing hurting readability?
  9. Is a parent changing font-size through em?
  10. Is text clipped by a fixed height?
Check loaded font and fallbackCheck size, line height, and measureCheck contrast and text decorationCheck wrapping, overflow, and clipping

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;
}