Skip to content

fix(layout): honour white-space:nowrap on inline elements - #295

Merged
tannevaled merged 1 commit into
mainfrom
inline-white-space
Oct 18, 2026
Merged

tannevaled merged 1 commit into
mainfrom
inline-white-space

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Problem

In a 400px <h1> (20px), <span style="white-space:nowrap">A rather long heading that would wrap over two lines otherwise</span> still wrapped: h1 52px tall, span on two lines. white-space:nowrap on the h1 itself gave one line.

Spec read

  • CSS Text 3 §3: white-space applies to all elements and is inherited.
  • CSS Text 3 §5.5 (Line Breaking Details): a soft wrap opportunity at the boundary between two characters is controlled by the white-space property on their nearest common ancestor. One created by a character that disappears at the break (a space) is controlled by the box directly containing that character.

Root cause

Only the block container's white-space reached line breaking: blockOrInlineContents computed a single nowrap flag (layout/layout.go:518). Every inter-word space was a wrap opportunity (glueRun glued only items with SpaceBefore == 0), whatever the inline ancestors said.

Change

  • InlineItem.NoWrapBefore, set after collection by markNoWrap when the nearest common ancestor of an item and the previous one has white-space: nowrap or pre.
  • glueRun keeps such items in one run and counts the space between them. forceOne places a whole nowrap run that is wider than the line as one overflowing piece, instead of one word per line.
  • Boundaries around the span still wrap: the span moves to the next line as a whole.
  • Approximation: the space-at-the-boundary case uses the common ancestor rather than the space's own box. That only differs for a nowrap span ending in a space.

Checked

  • pre on an inline span already did not wrap: its preserved spaces are not break points. Covered by the test.
  • pre-wrap and pre-line are parsed as pre (css/parse.go:789), so a block with them never wraps. That is wrong: both should wrap. break-spaces is not parsed at all. Not fixed here; reported.

Tests

  • TestInlineWhiteSpaceNowrap (RenderHTML + script measuring h1/span heights): nowrap span → one line, and a break before a nowrap span (52,26). Failed on main (52,52 / 52,52). Controls: nowrap on the block, pre span, normal span.
  • TestInlineNowrapSpan (layout unit): an overflowing nowrap run stays whole. layout coverage 100.0%.

🤖 Generated with Claude Code

Only the block container's white-space decided whether a line could wrap,
so <span style="white-space:nowrap">…</span> inside a normal heading
still wrapped at every space. CSS Text 3 §3: white-space applies to all
elements; §5.5: the white-space property on the nearest common ancestor of
the two characters controls breaking at their boundary.

After collection, each item whose common ancestor with the previous item
is nowrap/pre gets NoWrapBefore: glueRun keeps such items in one run
(counting the space between them), and forceOne places a whole nowrap run
that is wider than the line as one overflowing piece instead of tearing
it apart one word per line. Boundaries around the span still wrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 5514b12 into main Oct 18, 2026
7 checks passed
@tannevaled
tannevaled deleted the inline-white-space branch October 18, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant