Skip to content

[iOS][New Arch] Text without numberOfLines clips the last word when the final line is a single short "orphan" token (NSLineBreakByClipping) #57501

Description

@wilsojaw

Description

On iOS with the New Architecture (Fabric), a <Text> without numberOfLines will sometimes clip the last word of a paragraph off the right edge instead of wrapping it to a new line. The dropped word is not truncated with an ellipsis — it is drawn past the container's right edge and simply disappears.

It is deterministic per (content × width × font size): it happens only when a paragraph's natural word-wrap would leave a single short "orphan" token on the final line (e.g. a paragraph ending in "…the platform that is coming." where only coming. would land on the last line). Change the font size or the container width so the last line holds two-plus words, and it wraps correctly.

Root cause

RCTTextLayoutManager.mm-_textStorageAndLayoutManagerWithAttributesString:paragraphAttributes:size: sets:

textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
    ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode)
    : NSLineBreakByClipping;   // <-- used when numberOfLines is not set

With NSLineBreakByClipping, TextKit does not break the final line onto a new line when the remainder is a short orphan token — it keeps it on the previous line and clips it at the container's right edge. Because both the measurement pass (_measureTextStorage:) and the draw pass (drawAttributedString:) build the container the same way, onTextLayout even reports the crammed final line at exactly the container width (e.g. width: 396.0) — that clamped width is the observable signature of the merged/clipped line.

Switching that single line to NSLineBreakByWordWrapping (the mode already used implicitly on the happy path) fixes it with no other visible change. This is what apps that set numberOfLines={0}/omit it expect: normal word wrapping.

The offending line is still present on v0.86.0 and main (as of filing).

Steps to reproduce

  1. New Architecture enabled, iOS.
  2. Render a <Text> with a fixed width and no numberOfLines, containing a multi-line paragraph whose natural wrap leaves a single short word alone on the final line.
  3. Observe the last word is clipped off the right edge (not wrapped, not ellipsized).
  4. Nudge the font size or width so the last line would hold ≥2 words → wraps correctly.
import { ScrollView, Text } from 'react-native';

// Width + copy chosen so the final line is a lone short token ("coming.").
export default function App() {
  return (
    <ScrollView contentContainerStyle={{ padding: 22 }}>
      <Text style={{ width: 396, fontSize: 18, lineHeight: 30 }}>
        They are the chapters that build your character, deepen your faith, and
        prepare you for the platform that is coming.
      </Text>
    </ScrollView>
  );
}

coming. is clipped off the right edge instead of wrapping to its own line. (Exact clipping depends on font metrics + width; the invariant is "final line = one short orphan token." A custom/condensed body font makes it easy to hit; it is not font-specific in principle since the cause is the container's lineBreakMode.)

React Native Version

0.85.3 (confirmed the offending line is unchanged on 0.86.0 and main)

Affected Platforms

Runtime - iOS (New Architecture / Fabric)

Output of npx @react-native-community/cli info

SDKs:
  iOS SDK:
    Platforms: [ iOS 26.2, ... ]
Managers:
  CocoaPods: 1.16.2
IDEs:
  Xcode: 26.2/17C52
Languages:
  Java: 25.0.1
  Ruby: 2.6.10
npmPackages:
  react: 19.2.3
  react-native: 0.85.3

Host: macOS (Darwin 25.5.0), Node 22.21.1, Expo SDK 56 (newArchEnabled: true).

Stacktrace or Logs

No crash/log — purely a visual layout defect. onTextLayout reports the merged final line at exactly the container width (width === containerWidth), which distinguishes it from normal wrapping.

Reproducer

Minimal snippet above (single <Text>, fixed width, no numberOfLines, orphan-word final line). The fix is a one-line change in RCTTextLayoutManager.mm:

   textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0
       ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode)
-      : NSLineBreakByClipping;
+      : NSLineBreakByWordWrapping;

I can open a PR with this change if it's a welcome fix.

Screenshots and Videos

Before (clipped) vs after (patched to NSLineBreakByWordWrapping) — happy to attach if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: Author FeedbackNeeds: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions