# Performance — Speed Is Design
> A beautiful page that loads in 5 seconds reads as broken. Speed is not engineering garnish — it is part of the aesthetic. Quiet, fast, immediate: the same words that describe good design describe good performance. Agents over-ship: three fonts, a framework, and a chat widget for a page that could be HTML and 40KB of CSS. This file is the counterweight.
---
## Budgets (decide before building)
| Metric | Budget | Why |
|---|---|---|
| **LCP** | < 2.5s (mobile, 4G throttled) | The "is this page real?" moment |
| **INP** | < 200ms | Interaction feels instant, not sluggish |
| **CLS** | < 0.1 | Nothing jumps while reading |
| Page weight — marketing page | < 1 MB, and < 300 KB on the wire critical path | Respect the visitor |
| Page weight — content page | < 500 KB | Text is cheap; bloat is chosen |
| Fonts | ≤ 2 families, ≤ 4 files total, ≤ ~300 KB | See below |
| JS — mostly-static page | ≤ 50 KB, or **none** | If CSS can do it, CSS does it |
If a requirement breaks the budget, say so and cut the requirement — don't ship the slow version silently.
---
## Fonts (the #1 agent-made slowdown)
The full setup is in `typography.md` §Loading Fonts. The floor:
```html
```
```css
@font-face {
font-family: 'Inter';
src: url('/fonts/InterVariable.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
unicode-range: U+0000-00FF; /* subset to what you actually use */
}
```
Rules:
- **Variable font > family of static weights.** One file, every weight.
- Load **woff2 only.** No ttf, no eot, no woff fallback chain from 2015.
- `font-display: swap` (or `optional` for non-critical faces) — invisible text is a broken page.
- Preload **only** the display face used above the fold. Preloading everything defeats preloading.
- Google Fonts is acceptable for demos; self-host for production — privacy, one fewer origin, no third-party CSS chain.
- **Fallback metrics** kill the swap "jump" (`size-adjust`, `ascent-override`) — CLS goes to near zero:
```css
@font-face {
font-family: 'Inter-fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
```
---
## Images
Agents love full-bleed PNGs. Kill them:
1. **Format:** AVIF > WebP > JPEG. PNG only for flat graphics that SVG can't do.
2. **Responsive:** every content image ships `srcset` + `sizes`:
```html
```
3. **Reserve space:** `width` + `height` attributes (or CSS `aspect-ratio`) on **every** image. Unreserved images are the top cause of CLS.
4. **Lazy-load below the fold; never lazy-load the LCP image.** The hero image gets the opposite treatment:
```html
```
5. Hero/background images ≤ 200 KB after compression. If it can't compress, it should be CSS or SVG — see `imagery.md`.
6. `prefers-reduced-data` exists; treat giant decorative media as optional, not mandatory.
---
## CSS
- **One stylesheet** for a marketing page, hand-written, token-driven (`color.md`, `layout.md`). It will be smaller than any utility purge.
- No `@import` chains (serialized downloads). `` in `head`, once.
- The examples in `examples/` embed CSS in a single HTML file for portability. **In production, split it out** — page cacheability matters from visitor two onward.
- Critical CSS is a last resort for heavy pages, not a default. A 30KB stylesheet doesn't need inlining logic.
- `content-visibility: auto` on long below-the-fold sections is free render speed:
```css
.section { content-visibility: auto; contain-intrinsic-size: auto 600px; }
```
---
## JavaScript — Ship None If You Can
Ask in order:
1. **Does this need JS at all?** Menus (``), accordions (``), carousels (scroll-snap), tabs (radio inputs), dialogs (`