Skip to content

Add conic gradients to View backgroundImage - #58143

Open
Stringsaeed wants to merge 19 commits into
react:mainfrom
Stringsaeed:feat/conic-gradient
Open

Stringsaeed wants to merge 19 commits into
react:mainfrom
Stringsaeed:feat/conic-gradient

Conversation

@Stringsaeed

@Stringsaeed Stringsaeed commented Aug 25, 2026 •

Copy link
Copy Markdown

Summary

Adds conic-gradient() support to View’s backgroundImage style on iOS and Android.

Supports:

  • String and object syntax.
  • Rotation with from <angle>.
  • Center positioning with at <position>, including edge offsets, percentages, fractional values, and negative offsets.
  • Angular and percentage color stops, two-position stops, and transition hints.
  • Multiple background layers and existing background sizing, positioning, and repeat behavior.

Implementation

  • Adds JavaScript and C++ parsing, a shared ConicGradient model, and native prop conversion.
  • Uses CAGradientLayer on iOS and SweepGradient on Android.
  • Renders the iOS sweep on a square layer to preserve angles on rectangular views. A clipped container retains the original tile dimensions.
  • Clears conflicting center defaults when explicit right or bottom offsets are supplied.
  • Accepts negative center offsets on Android.
  • Rejects malformed commas, misplaced hints, excessive stop positions, and invalid colors in conic gradient objects.
  • Preserves rejection of px strings in linear/radial object color stops and transition hints. Numeric and percentage object positions remain supported.

RNTester includes 16 corner cases with string/object syntax switching, dimension swapping, and hide/show controls.

Scope

This PR does not add:

  • repeating-conic-gradient().
  • CSS color-interpolation methods such as in hsl.
  • Explicit normalization for color stops outside the visible 0–360° interval.

Changelog:

[General] [Added] - Support conic gradients in View backgroundImage.

Test Plan

Automated checks

  • yarn --cwd packages/react-native-codegen build: passed.

  • yarn build-types, yarn build-types --validate, and yarn test-generated-typescript: passed.

  • yarn flow-check: passed with 0 errors.

  • ESLint and Prettier for changed JavaScript files, C++ formatting for changed files, and the repository Kotlin formatting check: passed.

  • C++ API snapshots: regenerated all 12 variants with yarn cxx-api-build using Doxygen 1.16.1 and Python 3.12.14. Generation ran in a clean source copy to exclude ignored native build headers. yarn cxx-api-validate --snapshot-dir /Users/saeed/projects/react-native/scripts/cxx-api/api-snapshots --output-dir /private/tmp/pr58143-cxx-current-validation passed all 12 snapshots.

  • Fantom: all 796 tests passed across these six suites:

    yarn fantom 'View-conicGradient-itest|processConicGradient-itest|processBackgroundImage-itest|processBackgroundPosition-itest|/View-itest|View-nativeCSSParsing-itest' --runInBand

    Coverage includes both native CSS parsing modes and both C++ prop-setter modes.

  • Android: all 19 tests passed, including 12 conic-gradient tests and 7 color-stop tests:

    ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest
    --tests com.facebook.react.uimanager.style.ConicGradientTest
    --tests com.facebook.react.uimanager.style.ColorStopTest
    -Preact.internal.useHermesNightly=false -Pkotlin.incremental=false
    --console=plain --no-parallel

  • C++ CSS parser: all 39 CSSBackgroundImageTest tests passed, including five conic cases, using a standalone C++20 GoogleTest runner compiled from the current test source.

Validation limits

  • Main disables Android API validation and does not register apiDump or apiCheck; both task invocations reported task-not-found. The Android API snapshot is unchanged.
  • Building Fantom with the installed Xcode 27 toolchain required the local CMake flag -Wno-error=deprecated-literal-operator for a third-party JSON header. No source or build-script workaround is included.
  • The iOS pixel suite and simulator/emulator visual checks below were performed before this rebase and were not rerun for the object-validation fix.

Verified

Ran it locally: yes

The new tests reject positions: ['20px'] in both linear and radial object syntax, for colored stops and transition hints. They were run with the pre-fix parser and then the fixed parser:

yarn fantom processBackgroundImage-itest --runInBand --testNamePattern='px string'
Parser Result
Before the fix Both new regression tests failed; 77 existing tests passed.
With the fix All 79 tests passed.

Existing tests continue to cover numeric and percentage object stops, CSS pixel stops, and conic angular stops.

Earlier native rendering verification

RNTester was built and run on an iOS simulator and Android emulator. All 16 corner cases were checked, along with resizing, hiding, and restoring the gradient without remounting the view.

All 11 iOS pixel tests passed through a focused XCTest runner linked against the production renderer and gradient utilities. Four cases failed before the rectangular-rendering fix. This was not a run of the complete RNTester XCTest suite.

The screenshots below record that earlier verification.

Visual verification

Tested in RNTester on iOS and Android. The “Conic Gradient Corner Cases” example includes 16 selectable cases, string/object syntax switching, resizing, and hide/show controls.

Geometry and rotation

The red wedge should retain its 45° angle across aspect ratios. The 90° and −270° rotations should look identical.

Case iOS Android
Wide rectangle 01-wide-string 01-wide-string
Tall rectangle 02-tall-string 02-tall-string
Extreme aspect ratio 03-extreme-aspect-string 03-extreme-aspect-string
90° rotation 04-rotation-string 04-rotation-string
Equivalent −270° rotation 10-negative-rotation-string 10-negative-rotation-string
Center positioning

The white ring marks the expected center where it falls within the view. Negative offsets place the center above and left of the view, producing a solid-blue visible area.

Case iOS Android
Right 10px / bottom 20px - string syntax 05-edge-offsets-string 05-edge-offsets-string
Right 10px / bottom 20px - object syntax 05-edge-offsets-object 05-edge-offsets-object
Percentage offsets - string syntax 06-percentage-offsets-string 06-percentage-offsets-string
Percentage offsets - object syntax 06-percentage-offsets-object 06-percentage-offsets-object
Zero edge offsets 07-zero-offsets-string 07-zero-offsets-string
Fractional offsets 08-fractional-offsets-string 08-fractional-offsets-string
Negative offsets - string syntax 09-negative-offsets-string 09-negative-offsets-string
Negative offsets - object syntax 09-negative-offsets-object 09-negative-offsets-object
Color stops, tiling, and composition

Repeated rectangular tiles should retain independent, undistorted centers. The transition hint controls the blend midpoint, and transparent portions of the conic layer reveal the underlying linear gradient.

Case iOS Android
Repeated rectangular tiles 11-repeated-tiles-string 11-repeated-tiles-string
Transition hint 12-transition-hint-string 12-transition-hint-string
Mixed background layers 13-mixed-layers-string 13-mixed-layers-string
Invalid input and zero-size views

Invalid gradients should leave only the gray fallback background. A zero-size view should paint nothing and must not crash.

Case iOS Android
Invalid trailing hint - string syntax 14-invalid-hint-string 14-invalid-hint-string
Invalid trailing hint - object syntax 14-invalid-hint-object 14-invalid-hint-object
Excessive stop positions - string syntax 15-invalid-stop-string 15-invalid-stop-string
Excessive stop positions - object syntax 15-invalid-stop-object 15-invalid-stop-object
Zero-size view 16-zero-size-string 16-zero-size-string
Updates without remounting

Swapping dimensions should recalculate the gradient geometry. Hiding should reveal the fallback background, and restoring should redraw the gradient correctly.

Case iOS Android
Resized in place 17-resized-in-place 17-resized-in-place
Gradient hidden 18-hidden 18-hidden
Gradient restored 19-restored 19-restored

@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 Aug 25, 2026
@github-actions

Copy link
Copy Markdown

Warning

JavaScript API change detected

This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API.

  • Please include a clear changelog message.
  • This change will be subject to additional review.

This change was flagged as: POTENTIALLY_BREAKING

@Stringsaeed
Stringsaeed marked this pull request as ready for review August 29, 2026 21:56
@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 Aug 29, 2026
@Stringsaeed

Copy link
Copy Markdown
Author

Hey @intergalacticspacehighway, who help landing this PR?

Comment thread packages/react-native/React/Fabric/Utils/RCTConicGradient.mm Outdated
Comment on lines +24 to +28
if (gradient.position.top.has_value()) {
centerPoint.y = gradient.position.top->resolve(static_cast<float>(size.height));
} else if (gradient.position.bottom.has_value()) {
centerPoint.y = size.height - gradient.position.bottom->resolve(static_cast<float>(size.height));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we pass conic-gradient(from 0deg at right 10px bottom 20px, ...) the JS emits bottom 20, right 10 but BackgroundImagePropsConversions.cpp pre-sets position.top = 50% and position.left = 50% so bottom and right here would not override those values.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d3bc12e9f91. Explicit bottom now clears the default top, and explicit right clears the default left, while unspecified axes remain centered. Added regression coverage for string/object syntax across native CSS parsing and C++ prop-setter flag combinations, and verified the positioning in RNTester on both platforms.

@Stringsaeed
Stringsaeed force-pushed the feat/conic-gradient branch 2 times, most recently from 178d3e5 to 8a24d6f Compare September 29, 2026 23:47
@Stringsaeed

Copy link
Copy Markdown
Author

Do we need to separate the PR into smaller 2 PRs?

one for C++ foundation.
another one will be for complete cross-platform implementation.

@intergalacticspacehighway what do you think?

@intergalacticspacehighway intergalacticspacehighway left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. CCing @jorge-cab for the final decision.

@Stringsaeed

Copy link
Copy Markdown
Author

Hi @cipolleschi @cortinico — would either of you have a chance to take a look at this PR?

I’d especially appreciate feedback on the Fabric/native implementation and whether the overall API/approach fits where backgroundImage is heading.

Thanks!

@cortinico

Copy link
Copy Markdown
Contributor

Hi @cipolleschi @cortinico — would either of you have a chance to take a look at this PR?

That's a 3K LOC PR :/ plus this comes from out of the blue without an issue where we could have discussed this beforehand.

@Stringsaeed

Copy link
Copy Markdown
Author

That's a 3K LOC PR :/ plus this comes from out of the blue without an issue where we could have discussed this beforehand.

@cortinico

That's actually a good point, I should've opened an issue to discuss.

I'm happy to open one now so we can align on the direction first.

I can also split this PR into smaller gh stacked PRs if that would make it easier to review.

A good portion of code is tests/RNTester/API snapshots, but I agree the actual change is still fairly broad across JS, C++, iOS, and Android.

I thought we should decrease css gap as ongoing project in react-native, but maybe I have wrong context.

Would you prefer that I open the issue first and pause this PR until we agree on the approach?

@cortinico

Copy link
Copy Markdown
Contributor

Would you prefer that I open the issue first and pause this PR until we agree on the approach?

In general those changes start with a GitHub issue opened on this repo:
https://github.com/react-native-community/discussions-and-proposals

I'd love to know @intergalacticspacehighway opinion on this change as a whole also before we invest more time on it.

@psnet

psnet commented Oct 22, 2026

Copy link
Copy Markdown

Guys, this PR is cool addition to current gradients capabilities in RN and can be very helpful with diagrams etc
Could this be moved faster?

@intergalacticspacehighway

Copy link
Copy Markdown
Contributor

@cortinico The PR follows the existing backgroundImage/gradient patterns and platform APIs, so it fits in well. Looks good to me 👍

One small nit @Stringsaeed: Object-syntax linear/radial stops now accept px (e.g. positions: ['20px']). Before this PR they were rejected, and native CSS parsing still rejects them. We can keep the old behavior for the object path and add a test.

@cortinico

Copy link
Copy Markdown
Contributor

Let's start by rebasing this one as we can't merge it as is (there are several conflicts)

@Stringsaeed

Copy link
Copy Markdown
Author

@cortinico Updated ✅

@intergalacticspacehighway now we keeping the old behavior back

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.

4 participants