Controlling Text Wrap with text-wrap: balance and text-wrap: pretty
Use CSS text-wrap: balance for multi-line headlines and text-wrap: pretty for body copy widow prevention, replacing JavaScript text-balancing polyfills with a single declaration.
You are here because a headline wraps to four lines with one lonely word on the last one, and you want the CSS text-wrap property to fix it without touching JavaScript. The short answer: text-wrap: balance does that. But it only works on multi-line text. It has a hard limit of about six balanced lines. And text-wrap: pretty is the sibling you actually need for body copy. Apply balance to a single-line heading and nothing happens. That is the first failure mode, and it is in the specification as a deliberate guard. The browser will not reflow a line that has no break point to balance. So the real question is not “does it work” but “which declaration for which block”, and that is what settles here.
What text-wrap: balance actually does
The text-wrap shorthand sets two longhand properties: text-wrap-mode (whether wrapping happens at all, values wrap or nowrap) and text-wrap-style (how the rendering engine chooses the break points, values auto, balance, stable, and pretty). The balance value changes the line-breaking algorithm. Instead of greedily filling each line to its max-width and leaving a ragged right edge, the engine tries a small number of candidate break layouts and picks the one where the line lengths are most equal. That is why the formal syntax is text-wrap: balance | stable | auto | wrap | nowrap and why the specification calls it “balanced” rather than “centered” or “justified”. The text stays left-aligned. The right edge is smoother because the lines are closer to the same length.
Replace Every JavaScript Polyfill
h1 {
text-wrap: balance;
max-width: 30ch; /* or any value; balance works with any inline-size */
}
That single declaration replaces every JavaScript text-balancing polyfill you have ever written, including the ones that split text into span elements and measured each line. The polyfill approach required a resize observer, a loop over the measured widths, and a re-render on every viewport change. The CSS version is declarative, runs in the layout engine, and has no paint or composite cost beyond the initial layout pass. The cost is real but bounded: the spec says the engine may limit how many lines it attempts to balance, and in practice Chromium and WebKit stop around six lines. If your headline is longer than six lines, balance degrades to wrap behaviour. That is the accepted fallback.
text-wrap pretty widow prevention
text-wrap: pretty is the value for body text, not headlines. Its job is widow prevention. It stops a single word, or a very short final line, from dangling alone at the end of a paragraph. A widow is the typographic term for that orphaned last line, and it is the thing that makes a block of prose look unfinished. pretty does not try to equalise line lengths the way balance does. It only adjusts the break points of the last few lines so the final line has at least two words on it, or so the last line is not dramatically shorter than the one before.
article p {
text-wrap: pretty;
}
This is the value you want on long-form content. It is the one the performance-conscious developer should reach for. The layout cost of pretty is higher than auto because the engine may re-evaluate the final lines, but it is still cheaper than balance on a long paragraph. The spec allows the engine to bail out if the cost exceeds a threshold. The honest version: you describe what should happen under which conditions, the rendering engine is the final arbiter, and it decides when the balancing effort is not worth it. What you get in exchange is a ragged edge that is less ugly. That is a real improvement for readability even if it is not a mathematical guarantee.
CSS native text balancing: support and Baseline status
Current Support
Here is the precise state of the world. The balance and pretty values are Baseline widely available as of May 2025, which is the annual update to the Baseline status grouping. The WebKit engine shipped both in version 17.5 in May 2024, Chromium shipped them earlier, and Firefox followed. That means you can use them without a prefix in any engine that has shipped a release after those dates. The Baseline source is MDN compat data, and the status is categorical: widely available means all major engines have shipped it and more than 30 months have passed since the first stable release. If you are targeting an engine that predates that window, the accepted fallback is text-wrap: wrap, which is the initial value, or text-wrap: auto, which is the same behaviour with the default line-breaking algorithm.
Defensive Fallback
@supports (text-wrap: balance) {
h1 {
text-wrap: balance;
}
}
/* Fallback for older engines: */
h1 {
text-wrap: wrap;
}
The @supports guard is the right tool if you need to be defensive, but it is optional once you know your browser matrix. The interop story is clean here. This is not a feature where the engines disagree on the fundamental behaviour. The one quirk is that balance has no effect on single-line text. That is not a bug; it is the spec. A heading that fits on one line is already balanced. The engine does not waste layout time on it, which is why applying the property to a short label does nothing visible. If you need that label to look different, change the font size or the max-width, not the wrap style.
text-wrap balance vs pretty difference
The difference between balance and pretty is the typographic problem each solves. That distinction is the one you need to defend to a stakeholder who asks why a headline looks different from a paragraph. balance distributes text evenly across lines. It is for multi-line headlines, pull quotes, or any short block where you want the lines to look deliberate. pretty prevents widows in body text. It is for any paragraph longer than three lines where you want to avoid a single trailing word. The distinguishing feature is the goal: ragged edges versus orphans. One is about the shape of the whole block. The other is about the shape of the last line.
| Property | Best for | What it changes | Cost | Fallback |
|---|---|---|---|---|
text-wrap: balance |
Headlines, short multi-line text | Equalizes line lengths across the block | Higher; limited to ~6 lines | text-wrap: wrap |
text-wrap: pretty |
Body paragraphs, long-form prose | Adjusts final lines to avoid single-word widows | Moderate; may re-evaluate last lines | text-wrap: auto |
text-wrap: auto |
Default | Greedy line filling, no balancing | None | , |
text-wrap: stable |
Editable text | Keeps line breaks stable while editing | Low | text-wrap: auto |
text-wrap: nowrap |
Tags, labels, code | Disables wrapping entirely | None | white-space: nowrap |
Use the table as a cheat sheet. The one sentence to remember: balance for the headline, pretty for the paragraph, and never the other way around. Applying balance to a long paragraph triggers the balancing loop and then the engine bails out after six lines, leaving you with no benefit and a wasted layout pass.
CSS headline text distribution: how to set the max-width and inline-size
Headline text distribution is not just about the wrap style. It is about the box that contains the text. balance works best when the heading has a max-width or an inline-size that is reasonable for the font size. If the box is too wide, the engine has more lines to balance and the effect is diluted. If it is too narrow, the text wraps to many lines and balance gives up. The spec says the balancing cost is limited to avoid layout performance regression. A very long headline is exactly where you should not rely on it.
Card Titles And Hero Headings
.card-title {
font-size: clamp(1.5rem, 3vw, 2.5rem);
max-width: 22ch; /* a good length for a card title */
text-wrap: balance;
}
/* Or use logical properties for inline direction: */
.hero-heading {
inline-size: min(80vw, 600px);
text-wrap: balance;
}
The Two-To-Six-Line Sweet Spot
Set the inline-size so the heading wraps to at least two lines and at most six. If it wraps to one line, balance does nothing. If it wraps to seven or more, the engine may decide the balancing cost is too high and fall back to wrap. The sweet spot is three to four lines, where the eye sees the even distribution most clearly. For a hero section, that usually means a max-width of 20 to 30 characters per line, a standard measure for display type. The white-space property is still relevant as a reset: if you inherit white-space: nowrap from a parent, text-wrap: balance is overridden. Check the cascade before you debug.
Common mistakes and the honest caveats
Three Mistakes To Avoid
Two mistakes are the usual suspects. The first is applying text-wrap: balance to long body text. That is a performance cost with no visual payoff because the engine bails after six lines. The second is expecting balance to work across multiple block elements. It operates only within a single block container’s inline content. If you have a headline split across two <span> elements or two <p> tags, the engine balances each box independently, and the lines will not line up. The fix is to put the entire headline in one element.
<!-- This will NOT balance across the two spans: -->
<h2><span>The long</span> <span>headline text</span></h2>
<!-- This will balance: -->
<h2>The long headline text</h2>
The third mistake is forgetting that text-wrap: balance is not inherited by default. It is a non-inherited property. Set it on a wrapper, and the child headings do not get it unless you apply it to each selector. The inherit keyword works if you want to propagate it, but the initial value is wrap, and that is the fallback you get when you do nothing.
The Honest Caveat
The honest caveat is this: CSS text balancing is not a guarantee. The specification gives the engine permission to approximate, to limit the number of lines, and to fall back to the default algorithm when the cost is too high. You are not buying a pixel-perfect layout; you are buying a better default. The rendering engine is the final arbiter, and it will make the call. What you get is a new tool that removes the JavaScript dependency. The price is that you must accept the engine’s judgement on when balancing is worth it.
FAQ: text-wrap balance and pretty
When should I use text-wrap: balance instead of text-wrap: pretty?
Use balance for any headline, pull quote, or short display block that wraps to between two and six lines. Use pretty for paragraphs of body text, where the goal is widow prevention. The rule is simple: heading versus paragraph.
Does text-wrap: balance work on single-line text? No. It has no effect on single-line text, because there is no second line to balance against. This is a spec-level limitation, not a bug. If you need a single-line heading to look different, change the font size or the width.
What is the performance cost of text-wrap: balance? The cost is in the layout phase. The engine tries multiple break candidates and picks the best one, which is more expensive than the default greedy algorithm. The spec allows the engine to limit the balancing to a small number of lines, which keeps the cost bounded. For a six-line headline, the cost is a few extra layout passes, not a measurable jank.
Does text-wrap: pretty work in all engines?
It is Baseline widely available as of May 2025, so the answer is yes for any engine released after that date. For older engines, the fallback is text-wrap: auto, which is the default and produces a slightly uglier ragged edge but never breaks the layout.
Can I use text-wrap: balance with CSS Grid or Flexbox? Yes. The property works on the text inside any block, grid item, or flex item. It does not interact with the layout system; it only affects how the text wraps within its own box. The one caveat is that the box must have a defined inline-size, either from the grid track, the flex basis, or an explicit width.
What this replaces, and what it does not
This replaces the JavaScript text-balancing polyfills. The most common one was react-wrap-balancer, which split text into spans and measured line widths. That library is now unnecessary for any engine that ships text-wrap: balance. Delete the dependency. Remove the resize observer. Write one CSS declaration. Add text-wrap: balance to the heading component and text-wrap: pretty to the paragraph component in the style dictionary.
What it does not replace is a font-size strategy. balance makes the lines equal, but it does not make the text fit a specific number of lines. If you need a headline to be exactly two lines, you still control that with the font size, the max-width, and the words themselves. The CSS Text Module Level 4 specification, section 6.1, is the authority on the exact behaviour, and it is clear that the balancing is a hint, not a contract. The fallback is always text-wrap: wrap, which is the initial value. You cannot break a layout with this property. You can only improve it or leave it unchanged.