zudo-css-wisdom
GitHub repository

Type to search...

to open search from anywhere

Long URL and Path Wrapping

Created Apr 23, 2026Updated Apr 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

Takeshi TakatsudoCreated: 2026-04-24T07:26:15+09:00Updated: 2026-04-25T00:39:47+09:00