Repository navigation
Conversation
|
@jasozh has exported this pull request. If you are a Meta employee, you can view the originating Diff in D123471013. |
cortinico
approved these changes
Oct 7, 2026
cortinico
left a comment
Contributor
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
Summary: Pull Request resolved: react#58902 Fixes the issue described in [react#48552](react#48552), where a Pressable component can lose both its background color and ripple effect when borderless is true. ### Root Cause Android sets `isProjected() = true` for a borderless RippleDrawable so the ripple can extend beyond the view's bounds. React Native adds the background and ripple as layers in a CompositeBackgroundDrawable, where `isProjected() = true` if any layer is projected. With projection, both the background and ripple are drawn on the nearest native ancestor with a background. Given "root view -> parent view -> Pressable", the ripple and background should now be drawn on the parent. However, since the parent doesn't form a stacking context, Fabric flattens its children. The Pressable and parent become siblings, and the Pressable's background and ripple are now drawn on a further ancestor and consequently become hidden. Workarounds like opacity, `overflow: hidden`, and `collapsable={false}` work by establishing a stacking context so the parent remains the Pressable's ancestor. ### This PR Modify CompositeBackgroundDrawable to wrap `feedbackUnderlay` in a DrawableWrapper where `isProjected() = false`. The background and ripple are drawn in-place, so they remain visible without a stacking context. The ripple still extends beyond the view's bounds since `clipChildren = false` in React Native. This is gated behind the `fixBorderlessRippleAndroid` flag which defaults to false. ### Limitations With projection, the background and borderless ripple are drawn underneath all siblings. Without projection, they are now drawn over previous siblings and underneath following siblings. This matches behavior already shown by React Native foreground ripples, and Jetpack Compose also disables projection in `RippleHostView.UnprojectedRipple` and exhibits the same behavior. Disabling ripple projection also causes some behavioral changes with clipping. The ripple is no longer clipped to the outline of the ancestor it was projected on, and it can now be clipped by `overflow: hidden` or ScrollView ancestors it used to draw beyond. These changes also align with foreground ripples and Jetpack Compose. One alternative which more closely preserves projected behavior is to modify Fabric's core renderer logic to maintain a parent stacking context if a borderless ripple is present. However, this can potentially change stacking behavior in non-obvious ways depending on where a ripple is defined, where the nearest ancestor with a background exists, and siblings in between. This impacts zIndex behavior and makes Android's stacking diverge from iOS, where ripples don't exist. Changelog: [Android][Fixed] - Fix Pressable and TouchableNativeFeedback losing their background color and ripple when using a borderless ripple Reviewed By: cortinico Differential Revision: D123471013
jasozh
force-pushed
the
export-D123471013
branch
from
October 7, 2026 22:36
2ae30df to
479266c
Compare
This branch has not been deployed
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:
Fixes the issue described in #48552, where a Pressable component can lose both its background color and ripple effect when borderless is true.
Root Cause
Android sets
isProjected() = truefor a borderless RippleDrawable so the ripple can extend beyond the view's bounds. React Native adds the background and ripple as layers in a CompositeBackgroundDrawable, whereisProjected() = trueif any layer is projected.With projection, both the background and ripple are drawn on the nearest native ancestor with a background. Given "root view -> parent view -> Pressable", the ripple and background should now be drawn on the parent. However, since the parent doesn't form a stacking context, Fabric flattens its children. The Pressable and parent become siblings, and the Pressable's background and ripple are now drawn on a further ancestor and consequently become hidden.
Workarounds like opacity,
overflow: hidden, andcollapsable={false}work by establishing a stacking context so the parent remains the Pressable's ancestor.This PR
Modify CompositeBackgroundDrawable to wrap
feedbackUnderlayin a DrawableWrapper whereisProjected() = false. The background and ripple are drawn in-place, so they remain visible without a stacking context. The ripple still extends beyond the view's bounds sinceclipChildren = falsein React Native. This is gated behind thefixBorderlessRippleAndroidflag which defaults to false.Limitations
With projection, the background and borderless ripple are drawn underneath all siblings. Without projection, they are now drawn over previous siblings and underneath following siblings. This matches behavior already shown by React Native foreground ripples, and Jetpack Compose also disables projection in
RippleHostView.UnprojectedRippleand exhibits the same behavior.Disabling ripple projection also causes some behavioral changes with clipping. The ripple is no longer clipped to the outline of the ancestor it was projected on, and it can now be clipped by
overflow: hiddenor ScrollView ancestors it used to draw beyond. These changes also align with foreground ripples and Jetpack Compose.One alternative which more closely preserves projected behavior is to modify Fabric's core renderer logic to maintain a parent stacking context if a borderless ripple is present. However, this can potentially change stacking behavior in non-obvious ways depending on where a ripple is defined, where the nearest ancestor with a background exists, and siblings in between. This impacts zIndex behavior and makes Android's stacking diverge from iOS, where ripples don't exist.
Changelog: [Android][Fixed] - Fix Pressable and TouchableNativeFeedback losing their background color and ripple when using a borderless ripple
Reviewed By: cortinico
Differential Revision: D123471013