Skip to content

[iOS][Fabric] Text inside Modal keeps its old size after a live text size change (works on 0.86.3) #59375

Open

Description

@simongt

Description

On iOS with the New Architecture, Text inside a <Modal> doesn't follow a live system text size change. If the text size goes up while the app is open, every Text in the modal keeps the old size, while the TextInputs update. Relaunching fixes it. The same app on 0.86.3 updates correctly.

We hit this in our app through react-native-screens (expo-router native stack), whose screens take the same path, on the simulator and on an iPhone 16 Pro Max with iOS 26.7.1. There a later commit happens to repair the increase, but going from XXXL back to the default size leaves the screen large until it remounts.

What I found with logging in YogaLayoutableShadowNode on 0.88.0:

I haven't bisected 0.87, but #57246 moved the font scale handling into configureYogaTree, which is where this dirty mark starts. Related: #57512. I'm looking at a fix and will link a PR here.

Steps to reproduce

  1. Clone the reproducer, run npm install, then cd ios && bundle install && bundle exec pod install.
  2. Run a Release build on an iOS simulator at the default text size.
  3. With the app open, run xcrun simctl ui <udid> content_size extra-extra-extra-large (or change Settings > Accessibility > Display & Text Size > Larger Text).
  4. The text in the modal keeps its old size. Only the TextInputs grow.
  5. Relaunch the app at the same size. Now it's correct.

scripts/capture.sh <udid> runs steps 2 to 5 and saves the screenshots.

React Native Version

0.88.0

Affected Platforms

Runtime - iOS

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

System:
  OS: macOS 26.7.1
  CPU: (14) arm64 Apple M4 Pro
  Memory: 2.79 GB / 48.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.21.1
    path: /Users/simon/.nvm/versions/node/v22.21.1/bin/node
  Yarn:
    version: 1.22.22
    path: /Users/simon/.nvm/versions/node/v22.21.1/bin/yarn
  npm:
    version: 10.9.4
    path: /Users/simon/.nvm/versions/node/v22.21.1/bin/npm
  Watchman:
    version: 2026.05.25.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/simon/.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: Not Found
IDEs:
  Android Studio: 2025.3 AI-253.32098.37.2534.15336583
  Xcode:
    version: 27.1/27A9275
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.19
    path: /usr/bin/javac
  Ruby:
    version: 3.2.2
    path: /Users/simon/.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

No crash, layout only. Trimmed excerpt from the logging I added to YogaLayoutableShadowNode.cpp, on the live change to XXXL. "stored" is layoutMetrics.fontSizeMultiplier, "chain" is the Paragraph's Yoga owners nearest first, and * marks a dirty Yoga node.

pass root=RootView#11 stored=1.000 new=1.353
dirty node=Paragraph#4 stored=1.000 new=1.353 chain=View View ModalHostView* View RootView*
nolayout parent=View#34 child=ModalHostView#32 childStored=1.000 new=1.353 staleTextNodes=7

MANDATORY Reproducer

https://github.com/simongt/rn-modal-text-size-repro

Screenshots and Videos

Version Default size Live change to XXXL Relaunch at XXXL
0.88.0
0.86.3

iPhone 17 Pro simulator, iOS 26.5, Release builds with the default prebuilt core.

Activity

  1. r1shabhsharma05 commented on Oct 22, 2026

    @r1shabhsharma05

    /assign

  2. simongt commented on Oct 23, 2026

    @simongt
    Author

    Fix is up.

    After @javache's review, it now works inside the pass that already pushes the new font scale down the tree: as that pass unwinds, a view marks itself dirty when a child it just updated came back dirty, the same rule layout already follows when a parent is rebuilt. Hidden views are skipped, and it's still behind the font scale flag.


    @lazerg also opened this, with the same idea applied at the point where the child gets copied. Flagging it here so the two don't duplicate effort.

  3. lazerg commented on Oct 23, 2026

    @lazerg
    Contributor

    Closed mine

  4. kabal1000x commented on Oct 25, 2026

    @kabal1000x

    Same symptom without a <Modal>, on 0.88.0 iOS (Fabric), in an Expo SDK 58 app (React 19.3.0), on iPhone SE (3rd gen) and iPhone 17 Pro Max simulators, iOS 26.3. The screen is a plain stack screen (Expo Router / react-native-screens 4.30.0) holding a few Text and one TextInput with a placeholder. After switching Larger Text from the default to the largest accessibility size while the screen is mounted, every Text keeps its old size (checked at 1 s and 6 s), and the TextInput placeholder draws at the new size inside its unchanged box. A freshly mounted screen at the same setting lays out correctly. Mounted text catches up only when something else commits a layout change.

    Reading the 0.88.0 sources, there is a gap before configureYogaTree is ever reached, which #59383 would not cover:

    1. RCTFabricSurface observes UIContentSizeCategoryDidChangeNotification and calls _updateLayoutContext, which sets layoutContext.fontSizeMultiplier = RCTFontSizeMultiplier() and calls _surfaceHandler->constraintLayout(layoutConstraints, layoutContext). The constraints are unchanged.
    2. SurfaceHandler::constraintLayout commits oldRootShadowNode.clone(propsParserContext, layoutConstraints, layoutContext) and nothing else.
    3. RootShadowNode::clone dirties only on a constraints change:
      if (layoutConstraints != getConcreteProps().layoutConstraints) {
        newRootShadowNode->dirtyLayout();
      }
    4. RootShadowNode::layoutIfNeeded returns early when getIsLayoutClean(), so layoutTree → configureYogaTree (where the enableFontScaleChangesUpdatingLayout check now lives since Fix Fabric reusing nodes with a stale font scale (#57246) #57246) never runs for this commit.

    Before #57246, SurfaceHandler::constraintLayout on 0.86-stable called dirtyMeasurableNodes(*newRoot) when fontSizeMultiplier changed. #57246 removed that call and the helpers. main still has the 0.88.0 RootShadowNode::clone and SurfaceHandler::constraintLayout.

    #57124 (open, labelled Android) adds exactly the missing step in shared code, newRoot->dirtyLayout() when fontSizeMultiplier changes, so it looks relevant to iOS too, and possibly needed alongside #59383. The general report for this symptom is #45655.

    Screenshots (before / mounted after change / fresh mount) available; can supply a minimal repro on request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions