fix(fabric): textInput text color not adapting to appearance changes#2913
Merged
Saadnajmi merged 5 commits intomicrosoft:mainfrom Apr 11, 2026
Merged
Conversation
…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>
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>
JunielKatarn
approved these changes
Apr 11, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
labelColor) is always set on macOS, sinceNSAttributedStringdefaults to black unlike iOSviewDidChangeEffectiveAppearance(macOS) andhasDifferentColorAppearanceComparedToTraitCollection:(iOS) handlers to refresh text attributes on appearance changeRoot Cause
Three interrelated issues on macOS Fabric:
Missing foreground color:
RCTNSTextAttributesFromTextAttributesskipped settingNSForegroundColorAttributeNamewhen no explicitcolorprop oropacitywas set. On macOS,NSAttributedStringdefaults to black (unlike iOS whereUITextFieldprovides its own dynamic default), making text invisible in dark mode.No appearance change handler:
RCTTextInputComponentViewhad noviewDidChangeEffectiveAppearanceoverride on macOS, sodefaultTextAttributeswere never refreshed when switching light/dark mode.Frozen dynamic colors: The C++ color pipeline resolves dynamic colors (like
labelColor) to static values at creation time. Simply re-callingRCTNSTextAttributesFromTextAttributesafter an appearance change returns the same stale color. The fix detects when the foreground color is the default semanticlabelColor(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
colorprop shows visible textcolorprop (e.g."red","white") preserves its color across appearance changes🤖 Generated with Claude Code