zudo-css-wisdom

Type to search...

to open search from anywhere

Long URL and Path Wrapping

CreatedApr 23, 2026UpdatedApr 24, 2026Takeshi Takatsudo

Break long URLs, file paths, and package identifiers at delimiter-aware points without corrupting prose or code.

The Problem

Narrow containers — sidebars, tables, chat bubbles, callout boxes — routinely receive long unbroken tokens that the browser has no idea how to wrap:

@scope/org-name/packages/feature-module/src/components/widget.tsx
https://example.com/docs/guide/section?utm_source=newsletter&utm_medium=email&utm_campaign=launch
C:\Users\Example\AppData\Local\Temp\build-artifact-2026-04-24.log

By default, browsers treat each of these as a single word. The token blows past the container edge, forces a horizontal scrollbar on the whole layout, or spills into neighboring columns. AI agents reach for word-break: break-all or overflow-wrap: anywhere, ship the fix, and then get bug reports a week later: every paragraph is now breaking at random mid-word positions, and the prose is unreadable.

The real problem is that the browser does not know which tokens are URL-like and which are prose. A blanket rule fixes the narrow-container case at the cost of everything else on the page.

Why the One-Liners Fall Short

Each of these properties has a use, but none of them is a general-purpose solution:

PropertyWhat it doesWhy it isn’t the answer
word-break: break-allBreaks between any two characters, anywhereShreds prose mid-word; ignores delimiters; makes the rest of the page unreadable
overflow-wrap: anywhereSame effect, but only kicks in when the word would otherwise overflowStill delimiter-blind; a long word breaks at an arbitrary character rather than at / or ?
hyphens: autoInserts soft hyphens at dictionary-derived pointsDoes nothing for URLs or paths — they are not dictionary words
word-break: break-wordLegacy non-standard Chrome value; breaks more aggressively — roughly equivalent to word-break: normal plus overflow-wrap: anywhereNot a synonym for overflow-wrap: break-word; its emergency breaking is as aggressive as anywhere and just as delimiter-blind; avoid — use one of the standard properties above

The correct answer for a URL like https://example.com/docs/a/b/c is almost always “break after /.” The correct answer for a path like C:\Users\Example is “break after \.” None of the properties above know this.

The <wbr> Injection Strategy

<wbr> is the word break opportunity element. It tells the browser “if you need to wrap here, this is a fine place to do it.” When no wrap is needed, it produces zero visual output.

Verified properties of <wbr>:

  • No glyph rendered — it takes up no visual space
  • Not announced by screen readers — no a11y noise
  • Not copied to the clipboard — the user selects the URL and gets the original string back
  • The caret behaves normally during selection — no off-by-one cursor jumps
  • SSR-safe — just HTML, no client-side JavaScript required

The strategy is: find delimiter characters in URL-like tokens and inject a <wbr> after each one. The rendered string is byte-identical when copied. The token wraps at meaningful boundaries instead of at random character positions.

Before:

<code>@scope/org-name/packages/feature-module/src/widget.tsx</code>

After:

<code>@scope/<wbr>org-name/<wbr>packages/<wbr>feature-module/<wbr>src/<wbr>widget.tsx</code>
D1 — Four wrapping strategies in a 240px column

The plain cell overflows horizontally. break-all wraps at arbitrary character positions — the URL is unreadable. anywhere wraps at similar arbitrary positions but only when overflow would occur. smart-break wraps cleanly after each /, ?, and &.

D2 — Same path with and without wbr injection in a narrow sidebar

Delimiter Set

/ is the primary delimiter — it covers URLs, UNIX paths, and package identifiers. The full set worth considering:

DelimiterWhy inject after it
/Primary. URL path segments, UNIX paths, scoped package names
\Windows paths (C:\Users\Example\...)
.Dotted identifiers (com.example.app), filenames, hostnames
-Kebab-case identifiers, long slugs
_snake_case identifiers
:Port separators (host:8080), protocol (https:), namespace (ns:value)
?Query-string boundary
#Fragment boundary
&Query-parameter separator
=Query key/value boundary

Inject the <wbr> after the delimiter, not before. The delimiter stays on the preceding line, and the next segment starts the following line — the natural reading order.

The isPathLike Gate (Prose Guard)

Injecting <wbr> after every /, ., and - in a document corrupts normal prose:

  • and/or becomes and/<wbr>or
  • well-known becomes well-<wbr>known
  • state-of-the-art becomes state-<wbr>of-<wbr>the-<wbr>art
  • 1.2.3-beta.4 becomes a confetti of breakpoints
  • UI/UX becomes UI/<wbr>UX

The rendering is usually unchanged because these tokens fit on one line — but the token is now a break-candidate in every narrow context downstream.

Gate injection on a path-like check. A token is path-like when it looks like one of:

  • Contains two or more / characters (/a/b/c, packages/widget/src)
  • Starts with a URL scheme (https://, file://, ftp://)
  • Starts with a Windows drive letter (C:\, D:\Users\...)
  • Contains ? followed by = (query string shape)

Examples that must remain untouched:

TokenPath-like?
and/orNo — only one /, no scheme
well-knownNo — single -, no slashes
state-of-the-artNo — dashes only
1.2.3-beta.4No — version string, not a path
UI/UXNo — single / with all-caps around it
/a/b/c/d/eYes — three or more /
https://example.com/docs/guideYes — scheme present
C:\Users\Example\logsYes — Windows drive
?q=hello&lang=jaYes — query shape
D3 — isPathLike gate: prose stays whole, paths get break opportunities

If you cannot confidently classify a token as path-like, do not inject. The cost of a false negative (one token stays unwrapped in a narrow container) is much lower than the cost of a false positive (prose breaks at unnatural points everywhere).

Container CSS Tuning

<wbr> only proposes break points. The container’s CSS decides what to do with them. Different surfaces want different container rules.

ContextRecommended ruleWhy
Prose (paragraphs, list items, cards)overflow-wrap: break-wordBreaks at <wbr> when the token would overflow; leaves ordinary words intact
Code blocks (<pre><code>)white-space: pre-wrap — accept horizontal scroll, or add overflow-wrap: anywhere as a user-toggled modepre-wrap preserves indentation and newlines; <wbr> is not enough for unbroken tokens inside pre-wrap
Diff viewers (+/- columns, narrow gutters)word-break: break-allCells must not overflow into siblings; delimiter-aware breaking is less important than fitting
Narrow flex panels (sidebars, chat bubbles)overflow-wrap: break-word plus min-width: 0 on flex childrenWithout min-width: 0, a flex item’s min-content size equals its longest word, and the item refuses to shrink

Warning: do not mix word-break: break-all with smart-break on the same element

word-break: break-all tells the browser “you may break between any two characters, period.” Once that rule is active, the browser breaks at whatever character position fits the line first — it never consults the <wbr> break-opportunity hints. The delimiter-aware injection is fully defeated. Pick one strategy per element: either smart-break (<wbr> + overflow-wrap: break-word) for delimiter-aware breaks, or word-break: break-all for “fit at all costs” surfaces like diff cells. Never both.

Warning: white-space: pre-wrap on fenced code defeats overflow-wrap: break-word

white-space: pre-wrap preserves literal whitespace and newlines, which is what you want for code blocks. The side effect is that long unbroken tokens (a 180-character URL inside a log line) no longer respond to overflow-wrap: break-word — the rule is specified to only break when needed, and pre-wrap makes the token technically “fit” on its own overflow-scrolling line. You have two choices for code blocks:

  • Accept horizontal scroll. The code stays byte-accurate; the user scrolls.
  • Offer an explicit opt-in mode that sets overflow-wrap: anywhere (stronger than break-word; will break mid-token if needed). Scope this to a user-toggled class so it does not become the default.

Do not reach for word-break: break-all on code blocks — it shreds the code mid-identifier on every line.

D5 — overflow-wrap: break-word vs anywhere on the same injected content

break-word keeps segments intact until a single segment would itself overflow — so delimited URLs wrap cleanly at /, ?, and &. anywhere is more aggressive: it contributes to min-content width, so a flex parent is allowed to shrink below the longest segment. Use anywhere only when break-word is not enough (a single segment is longer than the container) and you are willing to break mid-identifier.

Zero-Width Space (U+200B) — Text-Only Fallback

U+200B (zero-width space, also written &#8203;) is a character that introduces a break opportunity inside a string. It is not a peer recommendation to <wbr>. Use it only when HTML is not available — when the long string must live inside a plain-text surface that cannot contain elements.

Surfaces where HTML cannot be emitted:

  • title attribute values (browser tooltip text)
  • aria-label values
  • Plain JSON strings handed to a component that does not interpret HTML
  • CSV or text-file exports that must still wrap when displayed

Trade-offs vs <wbr>:

  • Clipboard pollution. U+200B is a real character. It is copied with the selection and pasted into the destination. Pasting a URL that contains zero-width spaces into a terminal or a URL field can fail in subtle ways.
  • String-length distortion. "hello".length vs "hel​lo".length — the second is 6. Validators, diff tools, and byte-count UIs will report different values than the user sees.
  • Weaker a11y guarantee. Screen readers mostly ignore it, but behavior varies by reader and version. <wbr> has a well-specified “no announcement” contract.
  • Caret behavior. Some editors treat U+200B as a navigable character; the caret may stop there during arrow-key navigation.

Rule of thumb: if you can emit HTML, use <wbr>. Reach for U+200B only for text-only surfaces. Document the decision in code comments so the next maintainer does not swap it back to a plain string and lose the wrapping.

D4 — U+200B wraps inside a plain-text surface

The left panel overflows or fails to wrap cleanly. The right panel wraps at the injected U+200B positions — same visual result as <wbr>, but copy-paste now carries the invisible characters.

Use <wbr> whenever HTML is available.

Common AI Mistakes

  • Reaching for word-break: break-all as a blanket rule. It fixes the one narrow container in front of you and quietly ruins every paragraph and code block on the rest of the site.
  • Forgetting min-width: 0 on flex children. A flex item’s default min-width: auto equals min-content, which is the widest word. A long URL in a flex column refuses to shrink, forcing the whole row to overflow. Setting min-width: 0 on the child unsticks it.
  • Injecting <wbr> globally without an isPathLike gate. Prose tokens like and/or, state-of-the-art, and 1.2.3-beta.4 become wrap candidates they should never be.
  • Treating U+200B and <wbr> as interchangeable. <wbr> is HTML-only; U+200B is a character in the string. The copy-paste and string-length implications are different.
  • Mixing smart-break with word-break: break-all on the same element. break-all ignores <wbr> hints — the delimiter-aware injection is wasted work.
  • Applying overflow-wrap: break-word to a <pre> or <code> block and expecting long tokens to wrap. white-space: pre-wrap is already in effect for those elements, and it neutralizes break-word for single long tokens. Accept scroll, or opt in to overflow-wrap: anywhere explicitly.

When to Use

When to smart-break (<wbr> + overflow-wrap: break-word)

Prose surfaces that contain path-like tokens: sidebars, cards, chat bubbles, tables with long-identifier columns, error-message panels, callouts. Gate injection on isPathLike. This is the default for any UI that renders user- or system-generated URLs and file paths.

When to use U+200B

Text-only surfaces where HTML cannot be emitted: title attributes, aria-label values, plain-string JSON fields consumed by non-HTML-aware components. Prefer <wbr> everywhere HTML is available. Document the choice at the call site so future maintainers understand the intent.

When to leave it as scrolling or pre-wrap (code and diffs)

Code blocks where byte accuracy matters — prefer horizontal scroll to synthetic break opportunities. Offer an opt-in “wrap long lines” toggle that switches on overflow-wrap: anywhere. Diff viewers with narrow gutters may need word-break: break-all on the cell content because fitting the column is more important than delimiter-aware breaking.

References

A reference implementation of the <wbr>-injection strategy with an isPathLike gate is tracked in zudo-doc (epic zudolab/zudo-doc#370, PR zudolab/zudo-doc#383).

Revision History