text-wrap, overflow-wrap and word-break: which one do I actually want?

A practical guide to CSS's confusingly-similar text wrapping and breaking properties, and when to reach for each one

I don’t know about you, but every time a long URL blows out a layout, I do the same dance: type word- or break, stare at the autocomplete, and pick one of word-wrap, word-break or overflow-wrap more or less at random until the overflow goes away. I’ve been writing CSS for a very long time and I still couldn’t have told you the actual difference between them without looking it up. So I finally looked it up properly, and wrote it down so I never have to again.

TL;DR: the mental model

  • text-wrap controls the overall line-wrapping strategy. Layout-level stuff: balanced headings, no orphans, or no wrapping at all.
  • overflow-wrap lets the browser break a too-long word as a last resort. Your fix for long URLs and user-generated garbage. Use this one 95% of the time.
  • word-break lets the browser break words anywhere, all the time. Aggressive. Mostly for CJK text or walls of hash-like strings.
  • 🚫 word-wrap doesn’t really exist. It’s a legacy alias for overflow-wrap. Stop typing it (I’m telling myself this as much as you).

If you just want the “which one do I use” cheatsheet, skip to the end.


overflow-wrap: the last-resort break

This is the one you almost always want. overflow-wrap controls whether the browser is allowed to break an otherwise-unbreakable string when it would otherwise overflow its container. Think long URLs, a veryLongCamelCaseIdentifier, or someone’s 47-character German compound noun.

.comment-body {
  overflow-wrap: anywhere;
}

The key word is last resort. Text wraps normally at spaces and hyphens like it always did; the break-anywhere behaviour only kicks in when a single word is longer than the line and there’s no other option. Normal prose is completely unaffected, which is exactly the behaviour you want for user-generated content.

It has three values:

  • normal: the default. Unbreakable words overflow. Hello, horizontal scrollbar.
  • break-word: break long words when they’d overflow.
  • anywhere: same as break-word, with one subtle difference (next paragraph).

break-word vs anywhere

The difference is intrinsic sizing. With anywhere, the browser counts those potential break points when it calculates the element’s min-content size. With break-word, it doesn’t.

In a plain block layout you’ll never notice. But inside flexbox or grid it matters a lot, because min-content sizing decides how small an item is allowed to shrink. A flex item containing a long URL with overflow-wrap: break-word can still refuse to shrink and blow out your layout, because its min-content width is the full unbroken word. The same item with anywhere shrinks happily, because its min-content is now roughly one character wide.

This is a close cousin of the classic min-width: 0 flexbox fix, and it’s why my default is anywhere rather than break-word.


word-break: the aggressive one

word-break sounds like it does the same thing, and this is where most of the confusion lives. The difference:

overflow-wrap breaks a word only when it has to. word-break: break-all breaks between any two characters whenever a line is full, whether the word would overflow or not.

With break-all, ordinary English words get chopped mid-word at the end of every line, with no hyphens and no regard for readability. You almost never want that for prose. Where it’s actually useful:

  • Tables or lists full of hashes, tokens, and other machine-generated strings, where “words” are meaningless and you just want dense wrapping.
  • CJK text handling, which is most of the reason the property exists.
.commit-hash {
  word-break: break-all;
}

Its values:

  • normal: default breaking rules.
  • break-all: break between any characters (except CJK text follows its own rules).
  • keep-all: the opposite direction, don’t break between CJK characters. No effect on non-CJK text.

There’s also a deprecated word-break: break-word value. It was a compatibility shim that behaves like overflow-wrap: anywhere. If you have it in a codebase, replace it.


text-wrap: the modern, layout-level one

text-wrap is the newest of the three and it’s answering a different question entirely. It doesn’t care about individual overflowing words. It controls the line-breaking strategy for the whole block.

h1, h2, h3 {
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

The useful values:

  • wrap / nowrap: wrap normally, or don’t wrap at all. text-wrap: nowrap is the modern spelling of what we’ve always used white-space: nowrap for.
  • balance: makes all lines roughly equal length instead of leaving a short straggler on the last line. Made for headings. Browsers limit it to short blocks (Chromium: six lines or fewer), so don’t bother putting it on paragraphs.
  • pretty: tells the browser it can spend more effort on line breaking to avoid typographic ugliness, most notably single-word orphans on the last line. Designed for body text, at a small performance cost.
  • stable: for editable or dynamic content. Lines above the edit point don’t re-wrap as you type.

Strictly speaking text-wrap is a shorthand for text-wrap-mode (wrap/nowrap) and text-wrap-style (balance/pretty/stable), but I’ve never needed the longhands.

My baseline for every new project these days:

h1, h2, h3, h4 {
  text-wrap: balance;
}

p, li {
  text-wrap: pretty;
}

Two lines, zero downside, and your headings stop looking accidental.


The neighbours

A few properties that hang around the same problem space and add to the confusion:

white-space controls how whitespace inside the text is handled (collapse it? honour newlines?) and whether text wraps at all. pre, pre-wrap and pre-line are still the tools for rendering code or preserving user-entered line breaks. Fun fact: it’s now officially a shorthand for white-space-collapse + text-wrap-mode, which explains the overlap with text-wrap: nowrap.

hyphens is the civilised alternative to breaking words. hyphens: auto breaks long words at dictionary-correct hyphenation points and adds a hyphen character. Needs a lang attribute on your HTML to work, and quality varies by language and browser. For justified or narrow-column text it looks far better than overflow-wrap’s brute-force chop. manual (the default) only breaks at ­ soft hyphens you’ve placed yourself.

<wbr> and &shy; are HTML-level hints for a single break opportunity. <wbr> marks “you may break here” (great inside a long URL you’re displaying), &shy; does the same but renders a hyphen when the break happens.

line-break is a CJK-specific strictness control for which characters can start or end a line. If you’re not typesetting Japanese, you’ll likely never touch it.

text-overflow: ellipsis isn’t a wrapping property at all. It truncates instead of wrapping. Needs overflow: hidden and a nowrap (or line-clamp for multi-line) to do anything.


The cheatsheet

I want…Use
Long URLs / user content not to blow out the layoutoverflow-wrap: anywhere
…and it’s inside flexbox/grid and still overflowingstill overflow-wrap: anywhere, not break-word; or check for a missing min-width: 0
Nicer wrapping for headingstext-wrap: balance
No orphan word on the last line of paragraphstext-wrap: pretty
Dense wrapping of hashes/tokens/code-ish stringsword-break: break-all
Words broken at proper hyphenation pointshyphens: auto + lang attribute
A break hint in one specific spot<wbr> or &shy;
Truncation with an ellipsis, not wrappingtext-overflow: ellipsis
To type word-wrapdon’t; it’s the legacy alias for overflow-wrap

What finally made it stick was realising the three properties operate at three different levels: text-wrap is about lines, overflow-wrap is about emergencies, and word-break is about rewriting the rules. Now when the autocomplete pops up I actually know which one I’m reaching for. Hopefully you do too.