Skip to content

feat(ios): honor textAlignVertical on Paragraph and multiline TextInput#56774

Open
quantizor wants to merge 4 commits into
facebook:mainfrom
quantizor:feat-ios-text-align-vertical
Open

feat(ios): honor textAlignVertical on Paragraph and multiline TextInput#56774
quantizor wants to merge 4 commits into
facebook:mainfrom
quantizor:feat-ios-text-align-vertical

Conversation

@quantizor
Copy link
Copy Markdown

@quantizor quantizor commented May 11, 2026

Summary:

textAlignVertical (and the CSS-style verticalAlign) has worked on Android since #34567, but iOS silently ignores it on both <Text> and multiline <TextInput>: every value paints content at the top of the host view. This PR closes both halves of the gap so a fixed-height Text or multiline TextInput with verticalAlign: 'middle' (or 'bottom') renders the same on iOS as on Android. Single-line TextInput (UITextField) already centers natively and is intentionally not routed.

Computation matches Android's existing TextLayoutManager.getVerticalOffset exactly: 0 for auto / top, (boxHeight - textHeight) / 2 for center, boxHeight - textHeight for bottom, and 0 when content overflows the box. This is the CSS Box Alignment Level 3 align-content algorithm with safe overflow on a block container.

Paragraph (<Text>)

The offset is applied at three sites in RCTTextLayoutManager.mm:

  • drawAttributedString shifts the paint origin for the background pass, glyph pass, and highlight rect path.
  • getEventEmitterWithAttributeString reverse-offsets the incoming touch point so hit-testing still resolves the correct character.
  • getRectWithAttributedString forward-offsets the enumerated link/button rects so VoiceOver focuses on the right region.

Keeping all three in lockstep avoids the class of bug where a centered link "renders below the touch target" or accessibility reads the wrong frame. Measurement is unchanged: alignment shifts the line-box stack inside the box, it doesn't alter intrinsic text size.

Multiline TextInput

The offset is applied via UIScrollView.contentInset.top inside RCTUITextView.layoutSubviews. This is the right hook on iOS: it shifts the default scroll origin without interfering with text layout, user scrolling, or RN's existing textContainerInset wiring (which is used for padding). When content grows past the bounds, the inset clamps to 0 and normal scrolling takes over.

RCTTextInputComponentView.updateProps reads paragraphAttributes.textAlignVertical (already parsed by BaseTextInputProps), maps the C++ enum to an ObjC bridge enum (RCTUITextViewTextAlignmentVertical), and pushes it to the backed RCTUITextView. The mapping is also re-applied when the backed view switches from single-line to multiline so the new view picks up the alignment on the same render. The default Auto / Top case short-circuits before reading contentSize so apps that don't use the feature pay zero per-layout overhead.

No public header changes; ABI is preserved.

Changelog:

[IOS] [ADDED] - Honor textAlignVertical (and the equivalent verticalAlign style) on <Text> and multiline <TextInput>

Test Plan:

Tested in a Fabric / Hermes V1 / new-arch Expo app on iPhone 17 simulator (iOS 26.4) at all three values, for both Text and multiline TextInput:

vertical-align Result
top (default) Content flush to top of host view (unchanged)
middle / center Content centered in host view
bottom Content flush to bottom of host view

For Text: decorations (underline, strike) follow the glyphs in all three positions. Touching anywhere on a centered or bottom-aligned link still emits the correct press event. Overflow (text taller than the box) falls back to top to avoid clipping content.

For multiline TextInput: the caret and placeholder sit at the requested position before any text is typed. Typing extends content naturally; once the content fills past the fixed height, scrolling takes over and the alignment offset clamps to top.

Related

Honor ParagraphAttributes.textAlignVertical when drawing the Fabric
Paragraph view on iOS. Mirrors Android's existing offset computation:
center the line-box stack vertically when 'center' is requested, push
to the bottom when 'bottom' is requested, fall back to top when the
text overflows. Shifts paint origin, highlight rects, hit-test point,
and link/button accessibility rects in lockstep.

Equivalent to CSS Box Alignment Level 3 align-content:
{start,center,end} with safe overflow on a block container.
@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 May 11, 2026
@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 11, 2026
quantizor added 2 commits May 11, 2026 14:15
Extend the textAlignVertical fix from Paragraph to multiline TextInput
so a tall fixed-height multiline field can sit centered or bottom-aligned
on iOS the same way it already does on Android. Single-line TextInput
(UITextField) centers natively, so this only routes when the backed view
is the multiline UITextView.

Offset is applied via UIScrollView's contentInset.top inside
RCTUITextView.layoutSubviews, which is the spec-correct surface: it
shifts the default scroll origin without interfering with text layout or
user scrolling. When content exceeds bounds the inset falls back to 0
(safe overflow per CSS Box Alignment L3 align-content).
Rename the ObjC property to `textAlignVertical` so the bridge matches
the JS prop and the C++ paragraph attribute exactly. The enum type stays
`RCTUITextViewTextAlignmentVertical` since it mirrors the C++ enum
class name.

Skip the contentSize read for the default Auto / Top case so the new
code adds zero per-layout work for apps that don't opt in.
@quantizor quantizor changed the title feat(ios): honor textAlignVertical on Paragraph feat(ios): honor textAlignVertical on Paragraph and multiline TextInput May 11, 2026
Adds RCTUITextView.textAlignVertical to the committed Apple C++ API
snapshots so validate_cxx_api_snapshots passes.
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. 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.

1 participant