Module 4: Typography & Text Styling
Text Properties: Size, Spacing, Alignment and Readability
The properties that control how text looks and reads, how line-height really works, and the settings that make long text comfortable.
Good typography is mostly restraint: a readable size, generous line spacing, a sensible line length, and few weights. The properties are simple. The craft is in knowing what values to give them and how they interact.
Size and weight#
body {
font-size: 1rem; /* 16px by default, scales with user settings */
font-weight: 400;
}
h1 { font-size: 2.25rem; font-weight: 700; }
Use rem for font sizes so the text respects the user's browser setting. Body text below 16px is hard to read on many screens. Headings are usually between 1.25 and 3 times body size, and you will rarely need more than five sizes in total.
Weight is a number from 100 to 900. The keywords map to numbers: normal is 400, bold is 700. If the font does not include the weight you ask for, the browser picks the nearest one, and if there is none, it synthesizes a fake bold by smearing the glyphs. Fake bold looks blurry. Load the weights you actually use.
font-style: italic has the same issue. A real italic is a separate design. Without it, the browser slants the regular glyphs.
Line height#
This property decides vertical rhythm, and it is the one beginners most often get wrong.
body { line-height: 1.6; }
A unitless number is a multiplier of the element's own font size. With font-size: 16px, a line height of 1.6 gives 25.6px per line.
Here is what happens inside a line:
line-height: 1.6 on 16px text → line box is 25.6px tall
▲ 4.8px half-leading (extra space above)
│
──┼── ascender
Hx│ glyphs (font's own height, ~16px)
──┼── descender
│
▼ 4.8px half-leading (extra space below)
The extra space (line-height minus font-size) is split in half above and below the text. This half-leading is why text looks vertically centered inside a box when line-height matches the box height.
Unitless versus length matters for inheritance. A unitless value is inherited as the multiplier, so each child recalculates from its own font size. A length (like 24px or 1.5em) is computed once on the parent and inherited as a fixed value.
body { line-height: 1.5em; } /* computed: 24px on 16px body, inherited as 24px */
h1 { font-size: 3rem; } /* 48px text with 24px lines: lines overlap */
body { line-height: 1.5; } /* inherited as 1.5, h1 gets 72px lines */
For body text, 1.4 to 1.7 is comfortable. Large headings need less, around 1.1 to 1.25, because big text with generous spacing looks loose.
Alignment#
.a { text-align: left; }
.b { text-align: center; }
.c { text-align: justify; }
.d { text-align: start; } /* follows writing direction: left in English, right in Arabic */
text-align aligns inline content inside a block. It does not center the block itself. If a paragraph with text-align: center still hugs the left edge, you are probably centering the text inside a box that fills the width, or the box is narrower than expected.
Avoid justify on narrow columns. It creates uneven word gaps, called rivers, that make reading harder, and browsers' hyphenation is not as good as print software.
Decoration, transform and spacing#
a { text-decoration: underline; text-underline-offset: 0.2em; text-decoration-thickness: 1px; }
.nav a { text-decoration: none; }
.label { text-transform: uppercase; letter-spacing: 0.08em; }
.title { text-transform: capitalize; }
text-decorationis a shorthand for line, style, color and thickness. Removing underlines from links withnoneis common, but it makes links harder to identify for people who cannot rely on color, so keep some other visible cue.text-transformchanges display only. The underlying text stays the same, so screen readers read it normally and copy-paste gives you the original case.letter-spacingadds space between characters. Useemunits so it scales with font size. Uppercase text benefits from extra tracking (about 0.05 to 0.1em). Do not add tracking to lowercase body text.word-spacingdoes the same between words, and is rarely needed.
Line length#
Long lines tire the eyes, because they have to travel too far to find the start of the next line. Aim for 45 to 75 characters per line, with about 65 as a good target. The ch unit makes that direct:
.prose { max-width: 65ch; }
Wrapping and overflow#
.a { white-space: nowrap; } /* never wrap */
.b { white-space: pre-wrap; } /* keep newlines and spaces, still wrap */
.c { overflow-wrap: anywhere; } /* break long words (URLs) to avoid overflow */
.d { hyphens: auto; } /* needs a lang attribute on html */
.e { text-wrap: balance; } /* even out line lengths in headings */
.f { text-wrap: pretty; } /* avoid orphan last words in paragraphs */
text-wrap: balance is supported in current versions of all major browsers and fixes headings that end in a single dangling word. A long unbroken string, like an email address or token, forces its container wider than the screen unless you allow it to break with overflow-wrap.
The font shorthand#
body { font: 400 1rem/1.6 "Inter", system-ui, sans-serif; }
Size and line-height are separated by a slash, and the family must come last. It is also a reset shorthand: it sets weight, style, size, line-height and family together and resets any others (like font-stretch) to their initial values. It is handy in a base rule, and a source of bugs elsewhere.
A small base typography#
:root {
--step-0: 1rem;
--step-1: 1.25rem;
--step-2: 1.563rem;
--step-3: 1.953rem;
}
body {
font: 400 var(--step-0)/1.6 system-ui, sans-serif;
color: #1a1a1a;
}
h1, h2, h3 {
line-height: 1.2;
text-wrap: balance;
}
h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
p, li { max-width: 65ch; }
a { text-underline-offset: 0.2em; }
A modular scale (each step multiplied by about 1.25) gives sizes that feel related to each other, without choosing every number by instinct.
Common mistakes#
- Pixel line heights on
body, which break larger headings and zoomed text. - Centering a block with
text-align. - Very long lines on wide screens. A 1400px paragraph is not readable.
- All-caps text with no letter-spacing.
- Faux bold and italic from missing font files.
- Removing underlines from links and relying on a slightly different color.
- Using
text-transformfor content that matters, like an acronym that screen readers should spell differently.
Practice#
- Set up a base typography for an article page: size, line height, 65ch line length and a heading scale. View it at three window widths.
- Make two headings with the same font size, one with
line-height: 1.8and one with1.15. Compare. Explain what you see. - Reproduce the line-height inheritance trap: set
line-height: 1.5emonbody, make a largeh1, and watch the lines overlap. Fix it with a unitless value. - Style an uppercase label with tracking, then confirm that the HTML text stays lowercase by copying it.