Skip to content

feat(CSS): animate backgroundImage gradients on iOS and Android - #10590

Closed
MatiPl01 wants to merge 4 commits into
@matipl01/css-gradient-stop-validationfrom
@matipl01/css-background-image-values
Closed

MatiPl01 wants to merge 4 commits into
@matipl01/css-gradient-stop-validationfrom
@matipl01/css-background-image-values

Conversation

@MatiPl01

@MatiPl01 MatiPl01 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Note

This pull request was authored by AI on behalf of @MatiPl01.

Summary

Adds CSSLinearGradient and CSSRadialGradient value types and registers backgroundImage as an array of them, so CSS animations and transitions of gradients run on the native CSS engine on iOS and Android.

Two gradients interpolate stop by stop when they have the same type, the same kind of direction, the same shape and size form, and color stops that pair up one to one with the same position units. Any other pair switches discretely at the animation midpoint (or at the start of a transition with allow-discrete). An omitted or empty endpoint is a gradient without stops, so animating to or from "no gradient" is a discrete switch as well and the placeholder never reaches React Native.

A stop whose color React Native resolves natively (PlatformColor, DynamicColorIOS) keeps its raw payload. The gradient then switches discretely instead of losing the color or reaching the renderer as a colorless stop.

Based on the native part of #10193 by @Titozzz. Runtime tests, the example screen and the docs are in #10591.

Test plan

Covered by the cssBackgroundImage on-device suite in #10591 (green on iOS and Android at the tip of this stack). Manual check:

<Animated.View
  style={{
    width: 120,
    height: 120,
    animationName: {
      from: { backgroundImage: 'linear-gradient(90deg, red, blue)' },
      to: { backgroundImage: 'linear-gradient(90deg, green, yellow)' },
    },
    animationDuration: '2s',
    animationDirection: 'alternate',
    animationIterationCount: 'infinite',
  }}
/>

The colors blend continuously. Replace the to value with 'none' and the gradient disappears at the midpoint.

Changelog

Entry added to the Unpublished section of packages/react-native-reanimated/CHANGELOG.md.

Array-valued style properties (boxShadow, transformOrigin) were split into
per-index keyframes during normalization, so an explicit empty array was
indistinguishable from an omitted one and the array interpolator only ever
knew about the animated layers. An explicit [] therefore showed the view's
own shadows, a to-only animation over a two-layer base lost the second
layer, and fillMode: 'none' restored a truncated list.

Arrays now stay whole through normalization; null marks an omitted
endpoint and resolves to the complete underlying array, [] is an explicit
empty value, shorter explicit lists are padded with the child default, and
the discrete fallback returns the authored endpoints. One incompatible pair
(inset vs outset) makes the whole list discrete. 'none' normalizes to []
for shadows and transforms, and the transition path distinguishes [] from
an unspecified value.
The object form of backgroundImage accepted a transition hint at the start
or end of the stop list, or next to another hint, although the string form
already rejected those placements. React Native does not check them either:
iOS throws on a leading colorless stop and Android draws it transparent.
Both forms also accepted gradients with zero or one color stop, which
Android cannot build a shader from.

Hints now need a color stop on both sides and a gradient needs at least two
color stops; the object form throws like its other validations, the string
form drops the gradient the way it drops every other invalid string.
Adds CSSLinearGradient and CSSRadialGradient value types and registers
backgroundImage as an array of them, so CSS animations and transitions of
linear and radial gradients run on the native CSS engine on iOS and
Android. Gradients with the same structure (direction kind, shape, size
representation, stop count and position units) interpolate stop by stop;
any other pair switches discretely, which also covers an omitted or empty
endpoint because the placeholder for an absent layer has no stops.

A stop whose color React Native resolves natively (PlatformColor,
DynamicColorIOS) keeps its raw payload: the gradient switches discretely
instead of losing the color or reaching the renderer as a colorless stop.

Based on the native part of #10193 by Titozzz.
@MatiPl01 MatiPl01 self-assigned this Sep 16, 2026
@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8a7299d2-044e-4307-9082-89b20d203f2d

📥 Commits

Reviewing files that changed from the base of the PR and between a427793 and d411bcd.

📒 Files selected for processing (6)
  • packages/react-native-reanimated/CHANGELOG.md
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/CSSValueVariant.cpp
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/complex/CSSBackgroundImage.cpp
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/complex/CSSBackgroundImage.h
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The change adds C++ value types for linear and radial backgroundImage gradients. It parses gradient data from dynamic and JSI values, serializes values, checks interpolation compatibility, and interpolates compatible lengths, positions, colors, and color stops. It registers backgroundImage with the CSS interpolator system and adds the required template instantiations. The changelog documents the new animation behavior.

Priority: ➖ Normal

Change: Feature

Merge Risk: ⚪ Minimal · up to d411b

No concrete merge-blocking behavior remains; the inspected gradient transition paths preserve valid endpoint output and support omitted stop positions.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly explains the added gradient value types, interpolation behavior, discrete switching, native color handling, testing, and changelog update. It directly matches the changeset.
Title check ✅ Passed The title clearly and concisely describes the main change: adding CSS animation support for backgroundImage gradients on iOS and Android.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MatiPl01

Copy link
Copy Markdown
Contributor Author

Closing: the backgroundImage feature stays in #10193, rebased on the prerequisite fixes (#10589, #10595 to #10597 and the whole-list discrete PR); the native-color and compatibility fixes from here become commits on that branch.

@MatiPl01 MatiPl01 closed this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant