CSS from the ground up

Text and fonts

A readable page is mostly four decisions: the font, the size, the line spacing and the width of the column.

Part 5 of 12

Beginner14 min readBack to Courses

Four decisions, in order

Long text lives or dies on its rhythm. The font sets the voice: a system font stack — system-ui, sans-serif — costs nothing to load and looks native everywhere. The size sets the effort: around 1rem for body text, 1.6 line-height for the space between lines.

Then the measure — how wide a line gets. Between 60 and 75 characters is the range eyes read comfortably; wider and the eye loses its place on the return sweep, narrower and every line feels breathless. A max-width in ch measures exactly that: 68ch is roughly the ideal.

The four decisions

body {
  font-family: system-ui, sans-serif;
  font-size: 1rem;
  line-height: 1.65;
}

main {
  max-width: 68ch;
}

Weight and alignment do the talking

font-weight carries emphasis: 400 is normal, 600 and 700 are the headings. Rather than reaching for a new font for every section, use two weights of one family and let size and space do the rest.

Left-aligned text is the default for a reason: ragged right edges give every line a clear start, which centred and justified paragraphs do not. Centre a heading if the design calls for it; leave paragraphs alone.

Sizes that scale

A page with three type sizes does not need six. Pick one scale and stick to it — body at 1rem, a heading near 1.25rem, a page title near 2rem — and the page looks deliberate without any single rule being clever.

Everything in rem also means everything follows the reader’s own setting, which is the closest thing to a free accessibility win in CSS.

A size scale is also a promise about hierarchy. If the page title, a section heading and the body text are all within a few pixels of each other, the reader has to work out the structure from position alone. Three clear steps — body, section heading, page title — are enough for almost every page, and the gap between the steps is what makes the hierarchy visible at a glance.

The space around the words

Line height is not the only space that shapes a paragraph. The gap between paragraphs is a separate decision, and it should be smaller than the gap between sections — otherwise every paragraph looks like a new topic. A margin-block of around one line on paragraphs, and two or three times that before a heading, gives the page a rhythm that the reader feels without naming.

That rhythm is why a fixed pixel margin is a poor default. When the reader enlarges the text, the words grow but a pixel gap does not, and the paragraphs crowd together. A margin in em or rem grows with the type, so the proportion survives the change. The same logic applies to the space above a heading: express it in the same unit as the text, and the heading keeps its breathing room at every size.

What a font stack is really saying

The browser walks the list left to right and uses the first family it can find. The last entry should be a generic family — sans-serif, serif, monospace — because that is the only entry guaranteed to exist. Without one, a missing font drops the page into the browser’s default and the layout shifts.

A system stack such as system-ui, -apple-system, "Segoe UI", sans-serif asks for the operating system’s own interface font first. That costs no download, matches the rest of the reader’s machine, and avoids the flash of unstyled text that a web font causes.

Leading travels with the text

line-height written as a number — 1.65 — is inherited as that number, and each element multiplies it by its own font size. Written as 26px, it is inherited as a fixed length, so a heading at twice the body size keeps the body’s line spacing and its lines collide.

Paragraph spacing is separate. A margin-block on paragraphs gives a clear pause between blocks, while line-height only opens the space between lines inside one. Using both is normal; using an enormous line height to fake paragraph spacing makes every line feel like its own thought.

Three type mistakes: a line-height in pixels on the body, which a larger heading then inherits unchanged; font-size in vw with no clamp(), so the text shrinks with the window until it cannot be read; and a measure set in px, which stops matching the character count the moment the reader changes their font size.

Try these next

  • Compare line-height: 1.6 and line-height: 26px on a heading twice the body size.
  • Set a paragraph width in ch, then change the page’s font family and watch the line length move.
  • Use clamp(1.75rem, 4vw + 1rem, 3rem) on a page title and narrow the window slowly.

Make it readable

The page runs the full width of the window, in whatever font the browser picked, with lines touching. Choose the font, ease the line spacing, and cap the reading width.

Live Preview
Console output will appear here...

3 left to do

  • The page chooses a font stack
  • The lines have room between them
  • The reading width is capped

font-family: system-ui, sans-serif; and line-height: 1.65; on the body; max-width: 68ch; on the main column.

A title that scales, a column that reads

Give the page a title that grows with the window but stops at both ends, set the body’s line spacing as a number, and cap the article at a comfortable measure. The starter has the colours; the type is still the browser’s default.

Live Preview
Console output will appear here...

3 left to do

  • The title scales between a minimum and a maximum
  • The body has a line height
  • The article has a measure

font-size: clamp(1.75rem, 4vw + 1rem, 3rem); on the heading, line-height: 1.65; on the body and max-width: 68ch; on the article.

ch is the width of the zero character in the current font, so a max-width in ch is a measure in characters — the one unit that means what a typographer means by line length.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is the comfortable range for a line of body text?