text-wrap: balance and pretty
Two values that hand line-breaking decisions back to the browser — one for headings, one for body copy, and they are not interchangeable.
The last-line problem
A default line-breaking algorithm is greedy: it fills each line as far as it can and moves on. That is fast and produces the two shapes typographers spend their careers avoiding — a five-word heading where the last line holds one word, and a paragraph whose final line is a single orphan hanging under a full measure.
The pre-2024 fixes were all bad in different ways. Manual <br> tags break at every viewport width except the one they were authored for. Non-breaking spaces glue words together and then overflow on narrow screens. JavaScript balancers measure, re-measure, and shift layout after paint. The text-wrap shorthand replaces all of it with a declaration the layout engine can act on before the first paint.
One shorthand, two longhands
text-wrap is a shorthand for text-wrap-mode and text-wrap-style. The mode decides whether text wraps (wrap or nowrap); the style decides how. All four style values are worth knowing:
auto— the browser's fastest algorithm, ignoring character counts. The initial value.balance— evens out the number of characters across lines.pretty— a slower algorithm that favours layout quality over speed, aimed at body copy.stable— while the user edits content, lines before the edit point stay put instead of the whole block re-wrapping.
/* shorthand */
.headline { text-wrap: balance; }
/* the same thing, longhand */
.headline {
text-wrap-mode: wrap;
text-wrap-style: balance;
}
stable is the value nobody reaches for and probably should: any contenteditable region or comment composer benefits from it, because re-flowing the paragraph above the cursor on every keystroke is disorienting.
balance has a line limit
Balancing is expensive — the browser has to count characters and try candidate break points — so engines cap how much text they will do it for. MDN documents the caps explicitly: six lines or fewer in Chromium, ten or fewer in Firefox. Past that limit the value stops applying and the text wraps normally.
This is why text-wrap: balance on body or * does nothing useful. It is not that the effect is subtle on long text; it is that the effect is absent, while you have still asked every short block on the page to pay for the computation. balance is a tool for headings, subheadings, card titles, pull quotes, captions, and buttons — anything that occupies a handful of lines.
pretty is for body copy
pretty exists for the case balance refuses: long-form paragraphs. Rather than equalising every line, it accepts the greedy shape and improves the end of the block. Chrome's implementation targets orphans — a paragraph that would end with a single word is adjusted so the last line carries two or more — and it also fixes up hyphenation when consecutive hyphenated lines appear at the end of a paragraph, adjusting earlier lines to make room. It accounts for justified text as well.
/* Body copy: fix orphans without paying the balance cost */
.prose p,
.prose li {
text-wrap: pretty;
}
/* Short blocks: even out the lines */
.prose h2,
.prose h3,
.prose blockquote,
.card-title {
text-wrap: balance;
}
The two values are not a spectrum with pretty as the weaker option. They optimise different things, and applying balance to a paragraph long enough to matter yields nothing at all.
Wiring it into a design system
Both values are safe to set at the component level rather than per-page, because an unsupported value is dropped at parse time and the surrounding declarations survive. There is no need for @supports guards.
@layer base {
h1, h2, h3, h4 {
text-wrap: balance;
max-inline-size: 24ch; /* keeps headings inside the balance cap */
}
p { text-wrap: pretty; }
[contenteditable] { text-wrap-style: stable; }
}
@layer components {
.btn { text-wrap: balance; } /* two-line button labels stay even */
}
Constraining the heading measure with max-inline-size does double duty: it improves readability and keeps headings under the line count where balance still applies. For the sizing side of that, see the clamp() function.
Browser support and strategy
The text-wrap shorthand is supported in Chrome 114+, Firefox 121+, and Safari 17.4+. The balance value is supported in Chrome 114+, Firefox 121+, and Safari 17.5+ — widely usable in production in 2026. The text-wrap-style longhand landed later, in Chrome 130+, Firefox 124+, and Safari 17.5+, so prefer the shorthand unless you specifically need to set mode and style independently.
pretty is the outlier. It is supported in Chrome 117+ and Safari 26+, and as of MDN's compatibility data it is not supported in Firefox at all. Firefox readers get default wrapping. Since the failure mode is "the paragraph looks the way it always did," that is an acceptable enhancement to ship — but do not describe it to a client as a fix that everyone will see.
- Set
balanceon headings and other short blocks; never globally. - Set
prettyon body copy; expect no effect in Firefox. - Use
stableon editable regions. - Delete any JavaScript balancer these replace — running both fights over the same line breaks.