Repository navigation
iOS: View nested in Text sits below the baseline since 0.82 (placed with 12pt Helvetica's descender) #59549
Description
Description
On iOS with the New Architecture, a View nested in a Text (an inline view) is placed below the text baseline instead of on it. Android puts the bottom of the same view exactly on the baseline. This started in 0.82.0 and is still the case on main.
The error depends on the text around the view, and follows this formula exactly:
error = |descender of the line's font| + baseline offset - 2.76
- 2.76 is the descender of 12pt Helvetica, the font TextKit fills in for the view's own run, because React Native never gives that run a font.
- The baseline offset is
(lineHeight - font.lineHeight) / 2when alineHeighttaller than the font's line height is set (RCTApplyBaselineOffsetraises the glyphs by that much), and 0 otherwise.
For 15pt system text that's 0.86pt without lineHeight and 3.91pt with lineHeight: 24; for 24pt text with lineHeight: 36, 6.71pt. Both terms grow with Dynamic Type: 14.15pt for that last one at the first accessibility size. The formula predicts the logged error of all 20 samples in the repro (10 layouts at two text sizes) to within 0.003pt, and the screenshots match it to the pixel.
Measured from the screenshots, as the view's bottom minus the baseline of its line, in points (positive is below):
| Sample | iOS 0.88.0 | iOS 0.88.0, Accessibility Medium | iOS 0.88.0 with the fix below | iOS 0.81.6 | Android 0.88.0 |
|---|---|---|---|---|---|
15 / no lineHeight |
+0.67 | +3.67 | 0 | 0 | 0 |
| 15 / 15 | +1.00 | +3.67 | 0 | -1.00 | 0 |
| 15 / 20 | +2.00 | +5.67 | 0 | 0 | 0 |
| 15 / 24 | +4.00 | +9.00 | 0 | 0 | 0 |
24 / no lineHeight |
+3.00 | +7.67 | 0 | 0 | 0 |
| 24 / 28 | +3.00 | +7.67 | 0 | 0 | 0 |
| 24 / 36 | +6.67 | +14.33 | 0 | 0 | 0 |
| The case from #53092 | +0.67 | +3.33 | 0 | -4.00 | 0 |
| 15pt text with a nested 24pt word | +3.00 | +7.67 | 0 | 0 | 0 |
| Wrapped 15 / 24, view mid-paragraph | +4.00 | +9.00 | 0 | 0 | 0 |
The letters sit exactly on the baseline onTextLayout reports in every row of every screenshot; only the inline view is off. The repro's README has the full formula-vs-measurement tables, the logs, and the scripts that check them.
Cause
Links are to main at f754dbd. _measureTextStorage: places each attachment at the bottom of its glyph rect plus the descender of the font on the attachment's own run (RCTTextLayoutManager.mm#L643-L653):
UIFont *font = [[textStorage attributedSubstringFromRange:range] attribute:NSFontAttributeName
atIndex:0
effectiveRange:nil];
frame = {
.origin =
{glyphRect.origin.x,
glyphRect.origin.y + glyphRect.size.height - attachmentSize.height + font.descender},
.size = attachmentSize};The bottom of the glyph rect is the bottom of the line, so this is the baseline only if font is the font that sets the line's descent and the glyphs aren't raised. Neither holds:
- The attachment's run has no font.
RCTNSAttributedStringFragmentFromFragmentgives an attachment fragment onlyNSAttachmentAttributeNameand the event emitter (RCTAttributedTextUtils.mm#L405-L444).NSTextStoragethen fixes the attributes and fills in UIKit's default font (NSFontAttributeName // UIFont, default Helvetica(Neue) 12inNSAttributedString.h). Loggingfonthere printsHelvetica 12.0 (descender -2.760)for every attachment, whatever the text's font is. - A tall
lineHeightraises the glyphs.RCTApplyBaselineOffset(RCTAttributedTextUtils.mm#L345-L403) raises them by(lineHeight - font.lineHeight) / 2withNSBaselineOffsetAttributeName. The bottom of the line plus a descender doesn't include that. - The line's descent can come from another font. With mixed font sizes on a line, the baseline sits above the line's bottom by the largest descender on it, not the descender of the view's own text.
Regression in 0.82.0
This formula came in with #53341 (6da351a, first in v0.82.0-rc.0), to fix #53092. Before that, Fabric used glyphRect.origin.y + [layoutManager locationForGlyphAtIndex:].y (0.81.6). The same screen on a fresh 0.81.6 app puts the view on the baseline to the pixel in 8 of the 10 rows, and all 10 at the accessibility size. It's only wrong (too high) when the view is taller than the line's ascent and lineHeight keeps the line from growing: the glyph rect then starts at the top of the view instead of the line. That's the case #53092 reported. #53341 brought it to within 0.67pt, but moved every other inline view below the baseline.
Fix
Place the attachment on the baseline TextKit actually laid its line out with: the line fragment's origin plus the attachment glyph's location within it. That includes the baseline offset, doesn't depend on any font, and is the baseline onTextLayout reports for each line. It's the 0.81 placement with the line's top in place of the glyph rect's.
NSUInteger glyphIndex = attachmentGlyphRange.location;
CGRect lineFragmentRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:NULL];
CGFloat baseline = lineFragmentRect.origin.y + [layoutManager locationForGlyphAtIndex:glyphIndex].y;
CGRect frame = {.origin = {glyphRect.origin.x, baseline - attachmentSize.height}, .size = attachmentSize};With this, the bottom of every inline view in the repro sits exactly on the baseline (0px off at both text sizes), including the #53092 case and the mixed-size line. I'm happy to open a PR with it.
Related
- fix(ios): align inline views with inherited fonts and line heights #58967 (open) also fixes this, by giving the attachment's run the text's font and subtracting the baseline offset. I applied it to 0.88.0 in the repro: it puts the view on the baseline in every single-font row, but leaves the mixed-size row 2.17pt low (3.88pt at Accessibility Medium), because that line's descent comes from the larger font. Reading the baseline from TextKit avoids that.
- Support inline views in Text with lineHeight (#59420) #59420 (open, behind
fixTextInlineViewLineHeight) subtracts the baseline offset but still uses the attachment run'sfont.descender, so going by the diff the|descender| - 2.76part would remain (0.86pt for 15pt text).
Steps to reproduce
git clone https://github.com/mozzius/inline-view-baseline-repro && cd inline-view-baseline-repro/ReproducerApp && yarn installcd ios && bundle install && RCT_USE_PREBUILT_RNCORE=0 bundle exec pod install && cd ..(building the core from source is only needed for the repro's patched switch; stock is upstream's code)yarn start, thenyarn ios- Each row is a
Textwith a blueViewnested in it. The red hairline is the line's baseline fromonTextLayout, and the number on the right is the view's bottom minus that baseline. In stock mode, every view hangs below the hairline the letters sit on. - Tap patched: the views move onto the baseline.
yarn androidshows every view on the baseline.
The minimal case:
<Text style={{fontSize: 15, lineHeight: 24}}>
HEXHOLM <View style={{width: 12, height: 12, backgroundColor: 'blue'}} />
</Text>React Native Version
0.88.0. The code is the same in 0.88.1, on main (f754dbd), and in every release since 0.82.0.
Affected Platforms
Runtime - iOS
Areas
Fabric - The New Renderer
Output of npx @react-native-community/cli info
System:
OS: macOS 27.0.1
CPU: (14) arm64 Apple M4 Pro
Memory: 202.77 MB / 48.00 GB
Shell:
version: 5.3.20
path: /opt/homebrew/bin/bash
Binaries:
Node:
version: 24.22.1
path: ~/.nvm/versions/node/v24.22.1/bin/node
Yarn:
version: 1.22.22
path: ~/.nvm/versions/node/v24.22.1/bin/yarn
npm:
version: 11.19.1
path: ~/.nvm/versions/node/v24.22.1/bin/npm
Watchman:
version: 2026.10.19.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.17.0
path: ~/.rbenv/shims/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 27.0
- iOS 27.1
- macOS 27.0
- tvOS 27.0
- visionOS 27.0
- watchOS 27.0
Android SDK:
API Levels:
- "35"
- "36"
- "37"
IDEs:
Xcode:
version: 27.1/27A9275
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.20.1
path: /usr/bin/javac
Ruby:
version: 2.7.6
path: ~/.rbenv/shims/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.2.0
wanted: 20.2.0
react:
installed: 19.3.0
wanted: 19.3.0
react-native:
installed: 0.88.0
wanted: 0.88.0
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
Stacktrace or Logs
The repro's patch logs every attachment's placement (iPhone 17 Pro simulator, iOS 26.5, default text size). The first line shows the attributes React Native gives the attachment's run before TextKit sees it; the rest show the text's font, the font the stock code reads (always 12pt Helvetica), and both placements:
[inline-baseline] attachment run attributes from React Native: EventEmitter, NSAttachment
[inline-baseline] stock: view 12.33x12.33 | text font .SFUI-Regular 15.00 (ascender 14.282, descender -3.618, lineHeight 17.900, leading 0.000), paragraph lineHeight 0.00, baseline offset 0.000 | line 0.000..17.900, glyph rect 0.000..17.900, glyph location y 14.282, baseline 14.282 | attachment font Helvetica 12.0 (descender -2.760) | stock bottom 15.141 (+0.858 from baseline), fixed bottom 14.282
[inline-baseline] stock: view 12.33x12.33 | text font .SFUI-Regular 15.00 (ascender 14.282, descender -3.618, lineHeight 17.900, leading 0.000), paragraph lineHeight 24.00, baseline offset 3.050 | line 0.000..24.000, glyph rect 0.000..24.000, glyph location y 17.332, baseline 17.332 | attachment font Helvetica 12.0 (descender -2.760) | stock bottom 21.240 (+3.908 from baseline), fixed bottom 17.332
[inline-baseline] stock: view 16.33x16.33 | text font .SFUI-Regular 24.00 (ascender 22.852, descender -5.789, lineHeight 28.641, leading 0.000), paragraph lineHeight 0.00, baseline offset 0.000 | line 0.000..28.641, glyph rect 0.000..28.641, glyph location y 22.852, baseline 22.852 | attachment font Helvetica 12.0 (descender -2.760) | stock bottom 25.881 (+3.029 from baseline), fixed bottom 22.852
[inline-baseline] stock: view 16.33x16.33 | text font .SFUI-Regular 24.00 (ascender 22.852, descender -5.789, lineHeight 28.641, leading 0.000), paragraph lineHeight 36.00, baseline offset 3.680 | line 0.000..36.000, glyph rect 0.000..36.000, glyph location y 26.531, baseline 26.531 | attachment font Helvetica 12.0 (descender -2.760) | stock bottom 33.240 (+6.709 from baseline), fixed bottom 26.531
[inline-baseline] stock: view 12.33x12.33 | text font .SFUI-Regular 15.00 (ascender 14.282, descender -3.618, lineHeight 17.900, leading 0.000), paragraph lineHeight 0.00, baseline offset 0.000 | line 0.000..28.641, glyph rect 0.000..28.641, glyph location y 22.852, baseline 22.852 | attachment font Helvetica 12.0 (descender -2.760) | stock bottom 25.881 (+3.029 from baseline), fixed bottom 22.852
The last line is the mixed-size row: 15pt text, but a 28.64pt line with a 24pt word on it. The full logs are in the repro's measurements/logs/.
MANDATORY Reproducer
https://github.com/mozzius/inline-view-baseline-repro
Screenshots and Videos
The single-line samples, at the default text size. Red hairline: the line's baseline from onTextLayout.
| iOS 0.88.0 | iOS 0.88.0, fixed | iOS 0.81.6 | Android 0.88.0 |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
At Accessibility Medium:
| iOS 0.88.0 | iOS 0.88.0, fixed |
|---|---|
![]() |
![]() |
Full screens: iOS 0.88.0, fixed, iOS 0.81.6, iOS 0.81.6 at Accessibility Medium, Android, Android at font scale 2.






Activity