Skip to content

fix(fabric): textInput text color not adapting to appearance changes#2913

Merged
Saadnajmi merged 5 commits intomicrosoft:mainfrom
Saadnajmi:fix-textinput-dark-mode-new-arch
Apr 11, 2026
Merged

fix(fabric): textInput text color not adapting to appearance changes#2913
Saadnajmi merged 5 commits intomicrosoft:mainfrom
Saadnajmi:fix-textinput-dark-mode-new-arch

Conversation

@Saadnajmi
Copy link
Copy Markdown
Collaborator

Summary

  • Fixes TextInput text color not updating when the system appearance changes (light ↔ dark mode) on the new architecture (Fabric)
  • Ensures the default text color (labelColor) is always set on macOS, since NSAttributedString defaults to black unlike iOS
  • Adds viewDidChangeEffectiveAppearance (macOS) and hasDifferentColorAppearanceComparedToTraitCollection: (iOS) handlers to refresh text attributes on appearance change

Root Cause

Three interrelated issues on macOS Fabric:

  1. Missing foreground color: RCTNSTextAttributesFromTextAttributes skipped setting NSForegroundColorAttributeName when no explicit color prop or opacity was set. On macOS, NSAttributedString defaults to black (unlike iOS where UITextField provides its own dynamic default), making text invisible in dark mode.

  2. No appearance change handler: RCTTextInputComponentView had no viewDidChangeEffectiveAppearance override on macOS, so defaultTextAttributes were never refreshed when switching light/dark mode.

  3. Frozen dynamic colors: The C++ color pipeline resolves dynamic colors (like labelColor) to static values at creation time. Simply re-calling RCTNSTextAttributesFromTextAttributes after an appearance change returns the same stale color. The fix detects when the foreground color is the default semantic labelColor (vs. a user-specified color) and replaces it with a fresh [NSColor labelColor], then re-applies the attributed text while suppressing React state reconciliation.

Test plan

  • Launch RNTester macOS in dark mode → TextInput with no color prop shows visible text
  • Toggle to light mode → text color adapts (dark text on light background)
  • Toggle back to dark mode → text color adapts (light text on dark background)
  • TextInput with explicit color prop (e.g. "red", "white") preserves its color across appearance changes
  • Both single-line and multiline TextInput work correctly
  • Newly typed text after appearance change uses the correct color

🤖 Generated with Claude Code

…n new architecture

On the new architecture (Fabric), TextInput text color was correct on initial
mount but didn't update when the system appearance changed (light ↔ dark mode).
Two root causes:

1. RCTNSTextAttributesFromTextAttributes skipped setting NSForegroundColorAttributeName
   when no explicit color/opacity was set. On macOS, NSAttributedString defaults to
   black (unlike iOS), making text invisible in dark mode.

2. RCTTextInputComponentView had no appearance change handler on macOS
   (viewDidChangeEffectiveAppearance), so defaultTextAttributes were never refreshed.
   Additionally, the C++ color pipeline resolves dynamic colors (like labelColor)
   to static values at creation time, so simply re-calling the attribute builder
   returns stale colors. The fix detects the default foreground color and replaces
   it with a fresh dynamic NSColor.labelColor, then re-applies the attributed text
   while suppressing state reconciliation to prevent React from overwriting the
   update with cached shadow tree values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Saadnajmi Saadnajmi requested a review from a team as a code owner April 10, 2026 22:51
@Saadnajmi Saadnajmi changed the title fix(macOS): TextInput text color not adapting to appearance changes on new arch fix(text input): textInput text color not adapting to appearance changes on new arch Apr 10, 2026
@Saadnajmi Saadnajmi changed the title fix(text input): textInput text color not adapting to appearance changes on new arch fix(fabric): textInput text color not adapting to appearance changes on new arch Apr 10, 2026
@Saadnajmi Saadnajmi changed the title fix(fabric): textInput text color not adapting to appearance changes on new arch fix(fabric): textInput text color not adapting to appearance changes Apr 10, 2026
Saadnajmi and others added 3 commits April 10, 2026 16:14
Remove unnecessary [NSColor labelColor] override - the C++ color
pipeline already preserves dynamic NSColor objects through
wrapManagedObject/unwrapManagedObject, so re-applying text attributes
from props is sufficient for appearance adaptation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…andling

On macOS, calling [NSColor colorWithAlphaComponent:] on dynamic system
colors (like NSColor.labelColor) converts them to static resolved colors,
preventing them from adapting to appearance changes (light/dark mode).

Since getEffectiveTextAttributes() always sets opacity=1 for TextInput,
the colorWithAlphaComponent: call was always triggered but was effectively
a no-op (multiplying alpha by 1.0). Skip it when opacity is exactly 1.0
to preserve the dynamic nature of system colors.

This is the root cause fix for TextInput text not adapting to appearance
changes — the previous viewDidChangeEffectiveAppearance handler was
re-applying the same pre-resolved static color on each appearance change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructure the opacity != 1.0f guard in RCTAttributedTextUtils.mm to
use #if TARGET_OS_OSX / #else / #endif so the upstream if-condition is
preserved verbatim in the #else branch. Restore inline code in
traitCollectionDidChange: to match upstream, keeping macOS additions
purely additive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getEffectiveTextAttributes() always sets foregroundColor (from
defaultTextAttributes()) and opacity = 1, so the condition
`textAttributes.foregroundColor || !isnan(textAttributes.opacity)` is
always true. The #if TARGET_OS_OSX else branch could never execute.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Saadnajmi Saadnajmi enabled auto-merge (squash) April 11, 2026 01:58
@Saadnajmi Saadnajmi disabled auto-merge April 11, 2026 01:59
@Saadnajmi Saadnajmi merged commit 4d114df into microsoft:main Apr 11, 2026
17 checks passed
@Saadnajmi Saadnajmi deleted the fix-textinput-dark-mode-new-arch branch April 11, 2026 02:12
Saadnajmi added a commit that referenced this pull request Apr 11, 2026
…anges (#2914)

## Summary
Backport of the changes from branch `fix-textinput-dark-mode-new-arch`
to `0.81-stable`.

See #2913 for full
details.

## Test Plan
Same as the original PR.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants