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 {