Skip to content

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

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

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 anchors typed text and the placeholder to the bottom of the paragraph line box, and sizes the single-line caret to the full line-box height instead of the font height.

Before After

fontSize: 16, lineHeight: 80. iOS 26 simulator, Fabric.

Why

The fix splits two ways because UIKit's draw paths don't all honor the same attributes:

Surface Honors NSBaselineOffsetAttributeName?
Multi-line typed text (UITextView) yes
Placeholder (single + multi, UILabel) yes
Single-line typed text + caret (UITextField / UIFieldEditor) no
  • Multi-line typed text + placeholder: apply a baseline offset to re-center the glyph. Multi-line typed text reuses the existing RCTApplyBaselineOffset helper that <Text> already uses, so per-fragment fonts (nested <Text> with different fontSize) are handled correctly.
  • Single-line typed text + caret: zero paragraphStyle.minimumLineHeight / maximumLineHeight per range on the attributed string handed to UIKit. UITextField then renders at the font's natural line height and its built-in vertical centering positions the glyph in the bounds; the caret rect — derived from the same line box — shrinks to match. Other paragraphStyle fields (alignment, indent) are preserved per range. The shadow node measures from TextInputState, so _updateState re-inflates stripped line heights from defaultTextAttributes before pushing — without that step the cell would shrink to the font's natural height after the first keystroke.

The typed-text fix lives in RCTTextInputComponentView._setAttributedString: (single Fabric entry point, preserves the _textOf:equals: early-return). The placeholder fix lives in _placeholderTextAttributes of each backing view.

Performance

When lineHeight ≤ font.lineHeight (common case): zero allocations — guarded out before any mutation.

Changelog:

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

Test Plan

RN Tester → TextInput → lineHeight baseline. Verified on iOS 26 simulator (iPhone 16 Pro / iPhone 17 Pro Max) on Fabric, including nested <Text> mixed-font cases. Confirmed cell height stays stable across edits.

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
@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch from 4e79973 to bf72714 Compare April 18, 2026 18:13
@thanhnd1o2
Copy link
Copy Markdown

Looking for it

@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch 2 times, most recently from 9bcb81f to c73a582 Compare May 4, 2026 18:54
@janicduplessis janicduplessis marked this pull request as ready for review May 4, 2026 20:31
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label May 4, 2026
@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch 2 times, most recently from acf8672 to e4b8473 Compare May 5, 2026 15:57
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Warning

JavaScript API change detected

This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API.

  • Please include a clear changelog message.
  • This change will be subject to additional review.

This change was flagged as: POTENTIALLY_BREAKING

@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch 2 times, most recently from 99c3b8b to 156b724 Compare May 5, 2026 20:09
…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 (or carrying a zero-line-height
  stub), because UIKit's typingAttributes drops the paragraph style between
  keystrokes — without the re-seed the helper sees maximumLineHeight == 0 and
  bails for typed content.

UITextField (single-line) typed text — does NOT honor NSBaselineOffsetAttributeName.
  Per-range zero out paragraphStyle.minimumLineHeight / maximumLineHeight on
  the displayed attributedText so UITextField uses the font's natural line
  height; its built-in vertical centering then positions the glyph in the
  bounds, and the caret rect (sized from the same line box) shrinks to match.
  Other paragraph-style fields (alignment, indent) are preserved so nested
  <Text> styling survives. The shadow node measures from state, so undo the
  strip in _updateState before pushing — restore stripped line heights from
  defaultTextAttributes so the cell height stays at the configured lineHeight
  across edits.

Placeholder on both UITextField.attributedPlaceholder (UILabel draw) and
RCTUITextView._placeholderView — both honor NSBaselineOffsetAttributeName.
  Add the offset computation to _placeholderTextAttributes on both backing
  views so the placeholder is centered to match (single string, single set
  of attributes — a direct computation is equivalent to RCTApplyBaselineOffset).

Bug only manifests on Fabric. Paper has had a similar baseline-offset fix in
RCTUITextView.attributedText since 0.66 (see RCTUITextView setAttributedText:),
which never made it into the Fabric component view.
@janicduplessis janicduplessis force-pushed the fix/ios-textinput-lineheight-baseline-offset branch from 156b724 to 3fbf76f Compare May 5, 2026 21:09
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. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants