diff --git a/public/og-default.png b/public/og-default.png
index bf1d4f12..ded2aecd 100644
Binary files a/public/og-default.png and b/public/og-default.png differ
diff --git a/public/og/checklist.png b/public/og/checklist.png
index 99d27d2d..91b48a7a 100644
Binary files a/public/og/checklist.png and b/public/og/checklist.png differ
diff --git a/public/og/spec.png b/public/og/spec.png
index 467432c5..d95fcbb4 100644
Binary files a/public/og/spec.png and b/public/og/spec.png differ
diff --git a/public/og/spec/foundations.png b/public/og/spec/foundations.png
index 07a97dc7..61734d3a 100644
Binary files a/public/og/spec/foundations.png and b/public/og/spec/foundations.png differ
diff --git a/public/og/spec/foundations/text-wrap.png b/public/og/spec/foundations/text-wrap.png
new file mode 100644
index 00000000..7d58d0eb
Binary files /dev/null and b/public/og/spec/foundations/text-wrap.png differ
diff --git a/src/content/changelog/2026-06-25-text-wrap.md b/src/content/changelog/2026-06-25-text-wrap.md
new file mode 100644
index 00000000..a6dea58a
--- /dev/null
+++ b/src/content/changelog/2026-06-25-text-wrap.md
@@ -0,0 +1,8 @@
+---
+title: New page on balanced text wrapping
+date: "2026-06-25"
+type: added
+relatedSlugs: [text-wrap]
+---
+
+Added a page on [balanced text wrapping](/spec/foundations/text-wrap/) — using `text-wrap: balance` on headings and `text-wrap: pretty` on body copy (CSS Text Module Level 4) so the browser breaks lines intelligently instead of stranding a lone word, with no manual `
` hacks and a clean fallback where unsupported. This site now ships both in its own stylesheet.
diff --git a/src/content/spec/foundations/color-scheme.md b/src/content/spec/foundations/color-scheme.md
index f00e741d..3b1dd725 100644
--- a/src/content/spec/foundations/color-scheme.md
+++ b/src/content/spec/foundations/color-scheme.md
@@ -6,7 +6,7 @@ summary: "Tells the browser which colour schemes your page is designed for. Prev
status: recommended
order: 95
appliesTo: [all]
-relatedSlugs: [theme-color, favicons, pwa-manifest, forced-colors]
+relatedSlugs: [theme-color, favicons, pwa-manifest, forced-colors, text-wrap]
updated: "2026-05-29T17:40:31.000Z"
sources:
- title: "HTML Living Standard — Standard metadata names: color-scheme"
diff --git a/src/content/spec/foundations/text-wrap.md b/src/content/spec/foundations/text-wrap.md
new file mode 100644
index 00000000..db59269c
--- /dev/null
+++ b/src/content/spec/foundations/text-wrap.md
@@ -0,0 +1,72 @@
+---
+title: "Balanced text wrapping"
+slug: text-wrap
+category: foundations
+summary: "Let the browser break headings and body copy intelligently with text-wrap: balance and pretty — no orphaned words, no manual line breaks, no layout shift."
+status: recommended
+order: 150
+appliesTo: [all]
+relatedSlugs: [color-scheme, font-loading, core-web-vitals]
+updated: "2026-06-25T00:00:00.000Z"
+sources:
+ - title: "CSS Text Module Level 4 — the text-wrap shorthand"
+ url: "https://drafts.csswg.org/css-text-4/#text-wrap"
+ publisher: "W3C CSS Working Group"
+ - title: "MDN — text-wrap"
+ url: "https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-wrap"
+ publisher: "MDN"
+ - title: "CSS text-wrap: balance"
+ url: "https://developer.chrome.com/docs/css-ui/css-text-wrap-balance"
+ publisher: "Chrome for Developers"
+ - title: "Better typography with text-wrap: pretty"
+ url: "https://webkit.org/blog/16547/better-typography-with-text-wrap-pretty/"
+ publisher: "WebKit"
+---
+
+## What it is
+
+`text-wrap` is a CSS shorthand that tells the browser _how_ to break a run of text across lines, beyond the default greedy "fill each line, then break". Two values matter for content:
+
+- **`text-wrap: balance`** — even out the number of characters per line so a block doesn't end on a lonely word. Best for short runs: headings, blockquotes, captions, card titles. Browsers cap it (six lines in Chromium, ten in Firefox), so it stays cheap.
+- **`text-wrap: pretty`** — optimise the _last few_ lines of a longer block to avoid orphans (a single word on the final line) and bad breaks. Intended for body copy.
+
+Both are part of the `text-wrap` shorthand in CSS Text Module Level 4, alongside `text-wrap-mode` (`wrap` / `nowrap`) and `text-wrap-style` (which carries `balance` / `pretty` / `stable`).
+
+```css
+h1,
+h2,
+h3 {
+ text-wrap: balance;
+}
+p {
+ text-wrap: pretty;
+}
+```
+
+## Why it matters
+
+- **Readability.** A heading that wraps "Balanced text\nwrapping" reads better than one that leaves "wrapping" stranded alone. Avoiding orphans and ragged breaks is a typographic baseline print has always had.
+- **No manual line breaks.** The common workaround — `
` or ` ` to force "good" wrapping — breaks the moment the viewport, font, or translated string changes. `balance` adapts to whatever width it is given.
+- **It degrades safely.** Unsupported browsers ignore the declaration and fall back to normal wrapping. There is no polyfill, no JavaScript, and no layout to repair. `balance` is also cheap — browsers cap it to a handful of lines — though `pretty` is not (see below).
+
+This site ships it: `text-wrap: balance` on spec headings and `text-wrap: pretty` on body paragraphs, in [`global.css`](https://github.com/jdevalk/specification.website/blob/main/src/styles/global.css).
+
+## How to implement
+
+Apply `balance` to short, heading-like elements and `pretty` to flowing prose. Set it globally in your base stylesheet; you do not need a feature query because the fallback is simply default wrapping.
+
+Reserve `balance` for short blocks — the browser stops balancing past its line cap, so using it on long paragraphs does nothing useful. Use `pretty` for the long stuff.
+
+Unlike `balance`, `pretty` is not free: it deliberately trades layout speed for typography, running a slower algorithm with no line cap, so the cost scales with how much text it touches. That is a fine trade for genuine body copy, but think before blanket-applying it to every text node on the page — scope it to your prose containers rather than a bare `*` or `p` selector across the whole document.
+
+## Common mistakes
+
+- **`balance` on long body text.** Past the browser's line cap it is a no-op, and where it does apply to long blocks it can cost layout performance. Keep it for headings and other short runs.
+- **Keeping old `
` hacks.** Manual breaks fight the browser's balancing and produce double breaks at some widths. Remove them once you adopt `text-wrap`.
+- **Expecting `pretty` everywhere.** Engine support for `pretty` trails `balance`; treat it as a progressive enhancement, never as something a layout depends on.
+- **Applying `pretty` indiscriminately.** It runs a slower wrapping algorithm by design, and unlike `balance` it has no line cap, so applying it site-wide carries a real layout cost. Reserve it for actual body copy; do not hang it off a universal selector.
+
+## Verification
+
+- In a supporting browser, resize a balanced heading: the lines stay evenly filled rather than leaving a one-word last line.
+- `caniuse.com/css-text-wrap-balance` — `balance` is Baseline across Chromium, Firefox, and Safari; `pretty` has narrower support.
diff --git a/src/content/spec/performance/font-loading.md b/src/content/spec/performance/font-loading.md
index 2f191667..c8967084 100644
--- a/src/content/spec/performance/font-loading.md
+++ b/src/content/spec/performance/font-loading.md
@@ -6,7 +6,7 @@ summary: "Self-host WOFF2 fonts, subset them, set font-display: swap so text is
status: recommended
order: 70
appliesTo: [all]
-relatedSlugs: [preload-prefetch-preconnect, critical-css, core-web-vitals]
+relatedSlugs: [preload-prefetch-preconnect, critical-css, core-web-vitals, text-wrap]
updated: "2026-05-29T20:27:54.000Z"
sources:
- title: "MDN — @font-face"
diff --git a/src/styles/global.css b/src/styles/global.css
index c9d9ce00..3e6c8eff 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -113,6 +113,10 @@ body {
margin-bottom: 0.75rem;
letter-spacing: -0.01em;
scroll-margin-top: 7rem;
+ /* Even out short heading wraps so they don't strand a lone word.
+ Falls back to normal wrapping where unsupported. See
+ /spec/foundations/text-wrap/. */
+ text-wrap: balance;
}
.prose-spec > :first-child,
.prose-spec h2:first-child,
@@ -126,6 +130,7 @@ body {
margin-top: 1.75rem;
margin-bottom: 0.5rem;
scroll-margin-top: 7rem;
+ text-wrap: balance;
}
.prose-spec h4 {
font-size: 1rem;
@@ -136,6 +141,9 @@ body {
}
.prose-spec p {
margin-bottom: 1rem;
+ /* Avoid orphans on the last line of body copy where supported;
+ ignored otherwise. See /spec/foundations/text-wrap/. */
+ text-wrap: pretty;
}
.prose-spec ul,
.prose-spec ol {