Skip to content

Fix Switch losing outline and box shadow when backgroundColor is set on Android - #59425

Open
shubh73 wants to merge 3 commits into
react:mainfrom
shubh73:switch-outline-props
Open

shubh73 wants to merge 3 commits into
react:mainfrom
shubh73:switch-outline-props

Conversation

@shubh73

@shubh73 shubh73 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

On Android, outline*, boxShadow and the borderRadius they're drawn with are kept in the CompositeBackgroundDrawable that BackgroundStyleApplicator installs as a view's background.

Current behavior. ReactSwitch.setBackgroundColor replaces the background with a new RippleDrawable, which throws away that composite and everything in it. Fabric applies a view's props in the order of its prop map, which depends on the set of prop keys, so whether a Switch keeps its outline depends on which props it has:

  • With {width: 60, height: 40, backgroundColor: 'white', outlineWidth: 3, outlineColor: 'blue', outlineOffset: 8}, the props are applied as outlineColor, outlineOffset, backgroundColor, outlineWidth. backgroundColor drops the color and offset, and outlineWidth then creates a new outline with the defaults, so the Switch mounts with a black outline and no offset. Updating outlineOffset later restores the offset, but the color stays black because outlineColor isn't sent again.
  • Any update to backgroundColor removes the outline and box shadow entirely, whatever order they were applied in.
  • thumbColor also sets the ripple's color. That color is lost when backgroundColor is applied after it, since the new ripple uses the background color, and it isn't applied at all when the ripple is inside a composite, since setThumbColor only checks whether the background itself is a RippleDrawable.

With this change.

  • When the background is already a CompositeBackgroundDrawable, setBackgroundColor replaces only its original background layer with the ripple, and keeps the other layers. CompositeBackgroundDrawable gets a withNewOriginalBackground, like its other withNew* methods.
  • ReactSwitch keeps the last thumb color and builds the ripple with it (or with the background color once thumbColor is removed), and setThumbColor finds the ripple inside the composite.

ReactSwitch is the only view in ReactAndroid that replaces its background in setBackgroundColor; the others go through BackgroundStyleApplicator. Moving the Switch's background color to BackgroundStyleApplicator.setBackgroundColor and its ripple to the feedback underlay would remove the special case, but it would also move the ripple above the border and change how the background is drawn, so this keeps the existing ripple and only stops it from replacing the other layers.

Changelog:

[ANDROID] [FIXED] - Fix Switch losing its outline, boxShadow and thumbColor ripple color when backgroundColor is set

Test Plan:

  • Unit tests. New ReactSwitchManagerTest (Robolectric): outline props set before the background color are kept; updating the background color keeps the outline; the background color keeps a box shadow; thumbColor sets the color of a ripple inside the composite; a thumb color set before the background color is kept; a removed thumb color isn't reused for the next background color. ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.views.switchview.ReactSwitchManagerTest": 6 passed. On the current code the first 5 fail (black instead of blue, no outline, the composite replaced by a RippleDrawable, the ripple left white instead of green twice); the last passes there, since the current code doesn't store the thumb color, and guards the new stored color.

  • ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest: 832 tests, 0 failures.

  • yarn format-check-kotlin passes.

  • Device: OPPO K12x 5G (CPH2667, Android 16), RNTester debug build, New Architecture, with the Switch above next to a View with the same style. Logging the prop setters and setBackgroundColor (not part of this PR) showed, for the Switch:

    setOutlineColor  value=-16776961 bg=RippleDrawable
    setOutlineOffset value=8.0 bg=CompositeBackgroundDrawable
    ReactSwitch.setBackgroundColor color=ffffffff replacing CompositeBackgroundDrawable
    setOutlineWidth  value=3.0 bg=RippleDrawable
    
    • Before: the Switch mounts with a black outline and no offset; after setting backgroundColor to yellow, its outline is gone, and a Switch with boxShadow: '0 0 0 6px red' loses its shadow.
    • After: the Switch matches the View on mount, after updating outlineOffset, and after setting backgroundColor.
    • With thumbColor: 'red' and the outline, long-pressing the Switch shows no ripple before (it's white, like the background) and a red ripple after.
  • Screenshots. Each image shows, left to right: on mount, after updating outlineOffset, and after setting backgroundColor to yellow. In each row the Switch is on the left and a View with the same style on the right; the middle row (no backgroundColor) is the case not fixed here.

    Before:

    Before

    After:

    After

    Long-pressing a Switch with thumbColor: 'red', backgroundColor: 'white' and a blue outline, on main (left; the outline is also black there, from the prop order above) and with this change (right):

    Ripple before and after
  • Not run: other devices and Android versions, and the old architecture, where outline and box shadow aren't supported. Clearing the stored ripple color when thumbColor is removed is covered by the unit test only, not checked on a device.

Not fixed here: a Switch with an outline and no backgroundColor draws no outline. Its default background is a projected ripple, so the whole composite is projected onto an ancestor and hidden behind flattened content. That's the same problem #58902 fixed for the feedback underlay behind fixBorderlessRippleAndroid, and it would need the same treatment for the original background.

🤖 Generated with Claude Code

…on Android

ReactSwitch.setBackgroundColor replaced the view's background with a new
RippleDrawable. Outline and boxShadow are drawn by the
CompositeBackgroundDrawable that BackgroundStyleApplicator installs as the
background, so it was discarded whenever backgroundColor was applied after
them. Fabric applies props in map iteration order, so depending on the keys a
Switch's outlineColor/outlineOffset could be dropped on mount (with a later
outlineWidth recreating the outline with its black, zero-offset defaults), and
any backgroundColor update removed the outline and box shadow entirely.

Swap the ripple into the composite's original background instead, and let
setThumbColor find the ripple inside the composite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 23, 2026
…is set

setBackgroundColor built the new ripple with the background color, dropping
a ripple color set by an earlier thumbColor. Also mutate the ripple before
recoloring it, since the theme's ripple may share its state with other
drawables loaded from the same resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 23, 2026
setThumbColor only stored non-null colors, so after thumbColor was removed the
next background color still built its ripple with the old thumb color. Also drop
the mutate() call: each RippleDrawable created from a resource copies its
RippleState, and setColor only writes to that copy, so recoloring it can't
affect other drawables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shubh73 shubh73 changed the title Fix Switch losing outline and box shadow when backgroundColor is set on Android Fix Switch losing border, outline and box shadow when backgroundColor is set on Android Oct 23, 2026
@shubh73 shubh73 changed the title Fix Switch losing border, outline and box shadow when backgroundColor is set on Android Fix Switch losing outline and box shadow when backgroundColor is set on Android Oct 23, 2026
@github-actions

Copy link
Copy Markdown

Visual regression

1 visual difference(s) at a352df56fd4e. Visual differences need maintainer approval. Review the report, then apply visual-change-approved.

ios: no capture

android transform/string/default: Changed, 1 px (0.000%)

diff

Expected and Captured
Expected Captured
expected captured

@meta-codesync

meta-codesync Bot commented Oct 23, 2026

Copy link
Copy Markdown

@Abbondanzo has imported this pull request. If you are a Meta employee, you can view this in D127266607.

This branch has not been deployed

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant