Skip to content

fix(ios): center TextInput text when lineHeight > fontSize on Fabric#56487

Draft
janicduplessis wants to merge 2 commits intofacebook:mainfrom
janicduplessis:fix/ios-textinput-lineheight-baseline-offset
Draft

fix(ios): center TextInput text when lineHeight > fontSize on Fabric#56487
janicduplessis wants to merge 2 commits intofacebook:mainfrom
janicduplessis:fix/ios-textinput-lineheight-baseline-offset

Conversation

@janicduplessis
Copy link
Copy Markdown
Contributor

@janicduplessis janicduplessis commented Apr 17, 2026

Summary

On iOS, when a TextInput has lineHeight > fontSize, UIKit misrenders three surfaces:

  1. Typed text — glyphs anchor to the bottom of the attributed-string line box instead of centering within it. On Fabric only for multi-line; single-line is affected on both arches.
  2. Placeholder — inherits the paragraph style from defaultTextAttributes and sits low, on both Paper and Fabric.
  3. Single-line caret — sized to the full paragraph line-box height rather than the font height, visibly taller than the glyph.

The approach depends on which UIKit rendering path draws the affected surface:

UITextView (multi-line) typed text — honors NSBaselineOffsetAttributeName

Call RCTApplyBaselineOffset in RCTTextInputComponentView._setAttributedString: to inject the offset. The helper already ships in Fabric (used by <Text> in RCTTextLayoutManager) but was never wired into the <TextInput> path; Paper fixed this in #38359.

Wiring in the call alone is not enough: each keystroke round-trips UIKit → Fabric state → back, and UIKit's typingAttributes drops NSParagraphStyleAttributeName. _updateState stores the stripped attributedText as an OpaquePointer in TextInputState, which the helper would otherwise read as maximumLineHeight == 0 and return early. Re-seed the paragraph style from defaultTextAttributes on ranges missing it before calling the helper.

Placeholder on both backing views — the UILabel-based draw paths honor NSBaselineOffsetAttributeName

Add the equivalent offset computation to _placeholderTextAttributes on RCTUITextField (used as attributedPlaceholder) and RCTUITextView (used as attributedText of a UILabel subview). Both backing views are shared between Paper and Fabric, so this single change covers both arches.

UITextField (single-line) typed text — the UIFieldEditor draw path does not honor NSBaselineOffsetAttributeName

Instead, override setAttributedText: on RCTUITextField to forward a copy with paragraphStyle's minimumLineHeight/maximumLineHeight zeroed out (and NSBaselineOffsetAttributeName removed) to super. UITextField then renders the line at the font's natural height and its built-in vertical centering positions the glyph correctly in the bounds. The caret rect, which UITextField sizes from the same paragraph-style line box, shrinks to the natural font height as a consequence — no separate caret override is needed.

_defaultTextAttributes (the local ivar) keeps the unmodified paragraph style so the placeholder path still sees the real lineHeight. Yoga's frame-height measurement is also unaffected — this changes only what UITextField uses for per-line rendering.

Changelog

[IOS] [FIXED] - Center typed TextInput text, placeholder, and single-line caret when lineHeight > fontSize.

Test Plan

RN Tester → TextInput → lineHeight baseline renders one single-line and one multi-line TextInput with fontSize: 16, lineHeight: 32. Before: placeholder sits low, typed glyphs anchor to the bottom of the line box, single-line caret overshoots the glyph. After: placeholder and typed text render vertically centered; single-line caret matches the glyph height and sits alongside it.

Paper typed-text behavior is unaffected (the Fabric hunk is isolated to RCTTextInputComponentView._setAttributedString:, and the RCTUITextField.setAttributedText: override is dormant when no paragraphStyle line-height is set). Android is unaffected.

Related: #38359, #39145, #53092.

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 17, 2026
@facebook-github-tools facebook-github-tools bot added the Contributor A React Native contributor. label Apr 17, 2026
@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch 4 times, most recently from 606f1d7 to 4e79973 Compare April 18, 2026 17:06
@github-actions
Copy link
Copy Markdown

Caution

Missing Changelog

Please add a Changelog to your PR description. See Changelog format

…ht > fontSize

On iOS, when a TextInput's lineHeight exceeds its font's line height, UIKit
anchors glyphs to the bottom of the attributed-string line box instead of
centering them within it. The same misalignment affects the placeholder.
On single-line UITextField the caret is also sized to the full line box.

This patch fixes all three surfaces. The approach varies by UIKit
rendering path:

UITextView (multi-line) typed text — honors NSBaselineOffsetAttributeName.
  Call RCTApplyBaselineOffset in RCTTextInputComponentView._setAttributedString:
  to inject the offset. Re-seed NSParagraphStyleAttributeName from
  defaultTextAttributes on ranges missing it, because UIKit's typingAttributes
  drops the paragraph style between keystrokes and _updateState round-trips
  the stripped attributedText through TextInputState — without the re-seed
  the helper sees maximumLineHeight == 0 and bails for typed content.

Placeholder on both UITextField.attributedPlaceholder (UILabel draw) and
RCTUITextView._placeholderView — both honor NSBaselineOffsetAttributeName.
  Add the offset computation to _placeholderTextAttributes on both backing
  views. The fix applies to both Paper and Fabric because the backing views
  are shared.

UITextField (single-line) typed text — the UIFieldEditor draw path does
NOT honor NSBaselineOffsetAttributeName, and it sizes the caret to the
paragraph-style line box height. Override setAttributedText: to forward a
copy with paragraphStyle's minimumLineHeight/maximumLineHeight zeroed out
(and NSBaselineOffsetAttributeName removed) to super. UITextField then
renders the line at the font's natural height and its built-in vertical
centering positions it correctly in the bounds; the caret rect likewise
shrinks to the natural font height. _defaultTextAttributes (the local
ivar) keeps the unmodified paragraph style so the placeholder path still
sees the real lineHeight. Yoga's frame-height measurement is unaffected.
@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch from 4e79973 to bf72714 Compare April 18, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Contributor A React Native contributor.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant