351 lines
12 KiB
Markdown
351 lines
12 KiB
Markdown
# Typography — The Design
|
||
|
||
> Typography does 80% of the work. Choose faces with care, set them with intention, and never let defaults decide for you.
|
||
|
||
---
|
||
|
||
## The Two-Typeface Rule
|
||
|
||
Pick **one display face** and **one text face**. Two total. Mono can be a third if needed (numbers, code, kickers).
|
||
|
||
**Why:** A page with three different typefaces reads as confused. A page with one great family used well reads as designed.
|
||
|
||
### How to choose
|
||
|
||
**Display face** — used in headlines, hero, section markers, big moments.
|
||
- Ask: does it have **character**? Would I recognize it on a poster?
|
||
- Avoid: anything that looks like default system fonts. Roboto, Open Sans, Lato — these are *fine* but not *chosen*.
|
||
- Test: render the brand name in the display face at 96px. Does it look like a magazine? A poster? An interface? Good. If it looks like a template — pick another.
|
||
|
||
**Text face** — used in body, UI, forms, captions.
|
||
- Ask: is it **legible at 14–16px** for sustained reading?
|
||
- Ask: does it have a **complete weight range** (400, 500, 600, 700) and a **good italic**?
|
||
- Avoid: thin weights under 400 for body text. Avoid display serifs as body.
|
||
|
||
**Mono face** (optional) — for numbers, code, kickers, metadata.
|
||
- Must have good tabular figures (numbers align in tables).
|
||
- Use for: pricing, statistics, code, timestamps, IDs, environment variables.
|
||
|
||
---
|
||
|
||
## Recommended Faces (free / open license where possible)
|
||
|
||
### Sans (modern grotesque / neo-grotesque)
|
||
- **Inter** — workhorse, free, full range
|
||
- **Söhne** — Linear/Stripe-quality, paid
|
||
- **Geist** — Vercel's, free, beautiful
|
||
- **GT America** — paid, super versatile
|
||
- **ABC Diatype** — paid, elegant
|
||
- **Helvetica Now** — paid, the modern Helvetica
|
||
- **Neue Haas Grotesk** — paid, the original spirit
|
||
- **IBM Plex Sans** — free, distinctive
|
||
|
||
### Serif (display)
|
||
- **GT Super** — paid, warm, magazine
|
||
- **Tiempos Headline** — paid, editorial
|
||
- **Söhne Serif** — paid, modern serif
|
||
- **Editorial New** — paid, newspaper
|
||
- **Domaine Display** — paid, NYT-class
|
||
- **GT Sectra** — paid, contemporary serif
|
||
- **Lora / Source Serif / Newsreader** — free, good
|
||
- **Fraunces** — free, expressive, quirky
|
||
- **Instrument Serif** — free, elegant display
|
||
- **Playfair Display** — free, classic, use sparingly
|
||
|
||
### Mono
|
||
- **JetBrains Mono** — free, dev-friendly
|
||
- **IBM Plex Mono** — free, well-balanced
|
||
- **Berkeley Mono** — paid, the gold standard
|
||
- **GT America Mono** — paid
|
||
- **Geist Mono** — free, Vercel
|
||
- **Iosevka** — free, condensed
|
||
- **Fragment Mono** — free, modern
|
||
|
||
---
|
||
|
||
## Pairs That Work
|
||
|
||
| Display | Text | When |
|
||
|---|---|---|
|
||
| **Söhne / Inter Display** | Söhne / Inter | Refined Minimal, SaaS |
|
||
| **GT Super / Fraunces** | Inter / Söhne | Editorial, magazine |
|
||
| **GT America** | GT America Mono | Refined Minimal, technical |
|
||
| **Helvetica Now** | Helvetica Now | Swiss, manifestos |
|
||
| **Geist** | Geist Mono | Technical, dev tools |
|
||
| **IBM Plex Sans** | IBM Plex Mono | Technical, docs |
|
||
| **Instrument Serif** | Inter | Editorial, soft premium |
|
||
| **Editorial New / Tiempos** | Inter | Publishing, journalism |
|
||
| **GT Sectra** | ABC Diatype | Editorial premium |
|
||
| **Manrope / Inter** | JetBrains Mono | Playful, modern SaaS |
|
||
|
||
### Pairs that almost never work
|
||
- ❌ Two different serifs (one display, one text)
|
||
- ❌ Two different sans-serifs from different schools (e.g., a humanist + a geometric)
|
||
- ❌ Display serif + heavy industrial sans
|
||
- ❌ Comic Sans + anything
|
||
- ❌ Script + anything (only for one-off flourishes, never headlines)
|
||
|
||
---
|
||
|
||
## Scale & Sizes
|
||
|
||
**Default modular scale:** 1.250 (Major Third) — comfortable for product UI.
|
||
**Editorial scale:** 1.333 (Perfect Fourth) or hand-tuned — for content-heavy pages.
|
||
|
||
### Suggested scale (px, base 16px, ratio 1.250)
|
||
|
||
| Token | Size | Use |
|
||
|---|---|---|
|
||
| `text-xs` | 12px | Captions, labels, microcopy |
|
||
| `text-sm` | 14px | UI secondary, table cells, footnotes |
|
||
| `text-base` | 16px | Body, paragraphs, inputs |
|
||
| `text-lg` | 20px | Lead paragraphs, large UI |
|
||
| `text-xl` | 25px | H4, subhead small |
|
||
| `text-2xl` | 31px | H3, subhead medium |
|
||
| `text-3xl` | 39px | H2 |
|
||
| `text-4xl` | 49px | H1, section markers |
|
||
| `text-5xl` | 61px | Large section H1 |
|
||
| `text-6xl` | 76px | Page hero (small) |
|
||
| `text-7xl` | 95px | Page hero (medium) |
|
||
| `text-8xl` | 119px | Page hero (large) |
|
||
| `text-9xl` | 149px | Editorial hero, posters |
|
||
|
||
### Hero size — choose deliberately
|
||
|
||
- **Confident / minimal:** `clamp(2.5rem, 5vw, 4rem)` — 40–64px
|
||
- **Strong:** `clamp(3.5rem, 6vw, 5.5rem)` — 56–88px
|
||
- **Bold / editorial:** `clamp(4rem, 8vw, 7rem)` — 64–112px
|
||
- **Magazine / poster:** `clamp(5rem, 10vw, 10rem)` — 80–160px
|
||
- **Always test:** if hero is set at the default 36–48px, it reads as a template. Push it.
|
||
|
||
### CSS clamp formula
|
||
|
||
```
|
||
font-size: clamp(<min>, <fluid>, <max>);
|
||
|
||
Example:
|
||
font-size: clamp(2.25rem, 5vw + 1rem, 4.5rem);
|
||
```
|
||
|
||
The fluid value uses `vw` so it scales with viewport, with the `+ rem` offset so it doesn't get tiny on small screens.
|
||
|
||
---
|
||
|
||
## Line Height (leading)
|
||
|
||
| Type | Line height |
|
||
|---|---|
|
||
| Display headlines (set tight) | **1.0 – 1.1** |
|
||
| Large H1 / H2 (60px+) | 1.05 – 1.15 |
|
||
| Standard headings (24–40px) | 1.15 – 1.3 |
|
||
| Lead paragraphs (18–22px) | 1.4 – 1.5 |
|
||
| Body copy (16–18px) | 1.5 – 1.65 |
|
||
| Small body / UI secondary (14px) | 1.45 – 1.55 |
|
||
| Captions / labels (12–13px) | 1.4 – 1.5 |
|
||
|
||
**Rule:** bigger the type → tighter the leading. Smaller the type → looser the leading. UI = around 1.4–1.5.
|
||
|
||
---
|
||
|
||
## Letter Spacing (tracking)
|
||
|
||
| Type | Tracking |
|
||
|---|---|
|
||
| Display headlines (large) | **-0.02em to -0.04em** (negative — pulls letters closer) |
|
||
| Standard headings | -0.01em to -0.02em |
|
||
| Body copy | **0** (default) |
|
||
| All-caps labels / kickers | **+0.05em to +0.12em** (positive — opens up) |
|
||
| Buttons (often all-caps small) | +0.02em to +0.05em |
|
||
| Numerical mono data | 0 (let the mono handle alignment) |
|
||
|
||
**Rule:** larger display type wants negative tracking. All-caps wants positive tracking. Body copy wants 0.
|
||
|
||
---
|
||
|
||
## Weights — Use With Restraint
|
||
|
||
A typeface has 4–9 weights. Use **2–3 max** per page. Here is the typical allocation:
|
||
|
||
- **400 (Regular)** — body copy, paragraphs, default UI
|
||
- **500 (Medium)** — buttons, labels, emphasized inline text, captions
|
||
- **600 (Semibold)** — subheads, H3/H4, important UI
|
||
- **700 (Bold)** — H1/H2, hero, key moments only
|
||
|
||
**Avoid:** 300 (Light) for body. Avoid 800/900 unless it's a display moment — and even then, only if the family is designed for it.
|
||
|
||
### When to bold, when to italic
|
||
|
||
- **Bold for hierarchy.** Italic for tone, foreign words, citations.
|
||
- **Italic in body:** titles of works, the *New York Times*, foreign phrases, internal thought.
|
||
- **Bold in body:** sparingly — for inline emphasis. Don't bold entire sentences; bold the word.
|
||
- **Display italic:** some serifs have a beautiful italic — use it for editorial pull quotes, byline accents.
|
||
|
||
---
|
||
|
||
## Color & Contrast for Type
|
||
|
||
- **Primary text:** ink color on surface. Contrast ratio **≥ 7:1** (AAA) where possible. **≥ 4.5:1** (AA) at minimum for body.
|
||
- **Secondary text:** muted ink. Contrast ratio **≥ 4.5:1** minimum.
|
||
- **Tertiary / placeholders:** even more muted — acceptable to dip to **3:1** for non-essential.
|
||
- **Never:** light gray (#999) on white for body. Use #6B6B6B at lightest.
|
||
- **Headlines:** can dip lower contrast (3.5:1+) for stylistic effect — but never for body.
|
||
- **Links:** color or underline, not just color (accessibility).
|
||
- **Focus state:** visible focus ring, 2px offset, accent color.
|
||
|
||
See `color.md` for palette construction.
|
||
|
||
---
|
||
|
||
## Special Treatments
|
||
|
||
### Drop caps
|
||
- Use only in long-form articles, editorial spreads
|
||
- 3–4 lines tall, set in display face
|
||
- Indent the rest of the paragraph
|
||
|
||
### Pull quotes
|
||
- Display face, 1.5–2x body size
|
||
- Left-aligned, often with rule lines
|
||
- Sometimes quote marks in a much larger size (decorative)
|
||
|
||
### Numerals
|
||
- Use **tabular figures** (`font-variant-numeric: tabular-nums`) for tables, pricing, statistics
|
||
- Use **lining figures** (default in most fonts) for headlines and prose
|
||
- Old-style figures (with descenders) are a beautiful editorial choice — use consistently
|
||
|
||
### Hyphenation & justification
|
||
- Left-align body. **Never justify body text** — it creates ugly rivers.
|
||
- Use `hyphens: auto` sparingly; better to enable it for narrow columns, disable for wide ones
|
||
- Use `text-wrap: pretty` (modern CSS) when available — improves line breaks
|
||
|
||
### All-caps
|
||
- For kickers, labels, navigation, small UI elements
|
||
- Always positive tracking (+0.05em+)
|
||
- Never for body. Never for headlines over 24px (reads as shouting).
|
||
|
||
### Underlines
|
||
- Default browser underlines on links are ugly. Replace with custom underlines:
|
||
- `text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px;`
|
||
- Or use a `border-bottom` on inline elements for more control
|
||
|
||
---
|
||
|
||
## Typography Anti-Patterns
|
||
|
||
| ❌ Don't | ✅ Do |
|
||
|---|---|
|
||
| `font-weight: 700` on every heading regardless of family | Use 600 for headings, 700 only for hero moments |
|
||
| Letter-spacing `0` on all-caps labels | Add `+0.05em to +0.12em` to all-caps |
|
||
| Default browser font stack (`-apple-system, sans-serif`) | Choose a face. Even Inter is a choice. |
|
||
| Two different type families from different schools | Stick to ONE family for display + text |
|
||
| Body text in a display serif | Use display serif for display only |
|
||
| Justified text in a narrow column | Left-align, ragged right |
|
||
| `font-size: 16px` hero headlines | Hero should be 60–160px |
|
||
| Heading set with `line-height: 1.5` (looks loose) | Tighten to 1.05–1.15 on display |
|
||
| Letter-spacing `-0.05em` on body text (cramped) | Use -0.02em max for body, more for display |
|
||
| Inline `style="font-size: ..."` everywhere | Define a scale in tokens, use them |
|
||
| Mixing px and rem inconsistently | Use rem everywhere (or use a token system) |
|
||
| Setting font-size on `<p>` manually | Let the base size + scale handle it |
|
||
| Italic body copy in a font with no italic (auto-faked) | Pick a face with a real italic |
|
||
|
||
---
|
||
|
||
## A Working CSS Setup
|
||
|
||
```css
|
||
:root {
|
||
/* Type tokens */
|
||
--font-display: 'GT Super', 'Tiempos', Georgia, serif;
|
||
--font-text: 'Inter', -apple-system, sans-serif;
|
||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||
|
||
/* Scale (1.250) */
|
||
--text-xs: 0.75rem; /* 12px */
|
||
--text-sm: 0.875rem; /* 14px */
|
||
--text-base: 1rem; /* 16px */
|
||
--text-lg: 1.25rem; /* 20px */
|
||
--text-xl: 1.5625rem; /* 25px */
|
||
--text-2xl: 1.953rem; /* 31px */
|
||
--text-3xl: 2.441rem; /* 39px */
|
||
--text-4xl: 3.052rem; /* 49px */
|
||
--text-5xl: 3.815rem; /* 61px */
|
||
--text-6xl: 4.768rem; /* 76px */
|
||
--text-7xl: 5.96rem; /* 95px */
|
||
|
||
/* Leading */
|
||
--leading-tight: 1.05;
|
||
--leading-snug: 1.2;
|
||
--leading-normal: 1.5;
|
||
--leading-loose: 1.65;
|
||
|
||
/* Tracking */
|
||
--tracking-tightest: -0.04em;
|
||
--tracking-tight: -0.02em;
|
||
--tracking-normal: 0;
|
||
--tracking-wide: 0.05em;
|
||
--tracking-widest: 0.12em;
|
||
}
|
||
|
||
body {
|
||
font-family: var(--font-text);
|
||
font-size: var(--text-base);
|
||
line-height: var(--leading-normal);
|
||
color: var(--ink);
|
||
background: var(--surface);
|
||
font-feature-settings: 'kern' 1, 'liga' 1;
|
||
text-rendering: optimizeLegibility;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
h1, h2, h3 {
|
||
font-family: var(--font-display);
|
||
line-height: var(--leading-tight);
|
||
letter-spacing: var(--tracking-tight);
|
||
font-weight: 600;
|
||
}
|
||
|
||
h1 {
|
||
font-size: clamp(3.5rem, 6vw + 1rem, 6rem);
|
||
}
|
||
|
||
h2 {
|
||
font-size: clamp(2.25rem, 4vw, 3.5rem);
|
||
}
|
||
|
||
.kicker {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
text-transform: uppercase;
|
||
letter-spacing: var(--tracking-widest);
|
||
color: var(--ink-muted);
|
||
}
|
||
|
||
.measure {
|
||
max-width: 65ch; /* reading measure */
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Loading Fonts
|
||
|
||
1. **Self-host** when possible (privacy, performance, no FOUT).
|
||
2. **Subset** to Latin (or relevant script). Don't load 9 weights of 9 fonts.
|
||
3. **Preload** the display face used above the fold.
|
||
4. **Use `font-display: swap`** to avoid invisible text.
|
||
5. **Variable fonts** when available — one file, full weight range.
|
||
6. **Fallback metrics** — set `size-adjust`, `ascent-override`, `descent-override` on fallback to minimize layout shift.
|
||
|
||
```html
|
||
<link rel="preload" href="/fonts/InterVariable.woff2" as="font" type="font/woff2" crossorigin>
|
||
```
|
||
|
||
```css
|
||
@font-face {
|
||
font-family: 'Inter';
|
||
src: url('/fonts/InterVariable.woff2') format('woff2-variations');
|
||
font-weight: 100 900;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
}
|
||
```
|