Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3ab90fb
Add guide for animating react-native-svg
MatiPl01 Jun 17, 2026
e695b24
Add TODO to bump reanimated/worklets back to stable after release
MatiPl01 Jun 17, 2026
0940c9f
Sync docs-worklets to the reanimated/worklets nightly
MatiPl01 Jun 17, 2026
95a8e76
Remove nightly-pin TODO comments from docs package.json files
MatiPl01 Jun 17, 2026
68412da
Format new docs with format:md
MatiPl01 Jun 17, 2026
567796d
Move gradient video paths to a const so format:md is idempotent
MatiPl01 Jun 17, 2026
547580d
Cap the gradient recording height so it doesn't balloon on code expand
MatiPl01 Jun 17, 2026
e0d0a97
Fit the gradient recording within its card (contain) instead of cappi…
MatiPl01 Jun 17, 2026
bb26fb7
Crop the gradient recording to its centered gradient (square card)
MatiPl01 Jun 17, 2026
472587d
Match the video card to the code height and center the cropped clip i…
MatiPl01 Jun 17, 2026
a856b33
Strip CSS module comments and note that shared values don't drive CSS…
MatiPl01 Jun 17, 2026
97f3812
Potential fix for pull request finding
MatiPl01 Jun 17, 2026
c33b75e
Potential fix for pull request finding
MatiPl01 Jun 17, 2026
7c742f9
Apply suggestions from code review
MatiPl01 Jun 17, 2026
cdf9b51
Contrast imperative shared values with declarative CSS, with an inlin…
MatiPl01 Jun 17, 2026
ac32146
Reformat SvgGradient and realign showLines after the added React imports
MatiPl01 Jun 17, 2026
6b9d75b
Reframe the overview comparison around CSS transitions (plain vs shar…
MatiPl01 Jun 17, 2026
9791e9f
Correct Polygon/Polyline web support in the SVG animation guide
MatiPl01 Jun 17, 2026
a34135e
Simplify the Polygon/Polyline web note in the SVG guide
MatiPl01 Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs-reanimated/docs/core/useAnimatedProps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import AnimatingPropsSrc from '!!raw-loader!@site/src/examples/AnimatingProps';

- You can share animated props between components to avoid code duplication.
- We recommend to create adapters outside of the component's body to avoid unnecessary recalculations.
- For animating [`react-native-svg`](https://github.com/software-mansion/react-native-svg) components, see [Animating SVG](/docs/guides/animating-svg).

## Platform compatibility

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import AnimationNameSrc from '!!raw-loader!@site/src/examples/css-animations/Ani

- At minimum 1 keyframe is required to create an animation. Reanimated will take the current state of the element as the first keyframe.
- If multiple animations target the same property, the animation later in the array will override changes from the previous one.
- You can animate [`react-native-svg`](https://github.com/software-mansion/react-native-svg) components too - see [Animating SVG](/docs/guides/animating-svg).

## Platform compatibility

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ import TransitionPropertySrc from '!!raw-loader!@site/src/examples/css-transitio

- We discourage the use of `all` property as it can lead to performance issues.

- You can also transition [`react-native-svg`](https://github.com/software-mansion/react-native-svg) props - see [Animating SVG](/docs/guides/animating-svg).

## Platform compatibility

<PlatformCompatibility android ios web/>
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ Check out the full example below:

<InteractiveExample src={AnimatingPropsSrc} component={AnimatingProps}/>

For a complete guide to animating `react-native-svg` - including CSS animations and transitions and which props are supported - see [Animating SVG](/docs/guides/animating-svg).

## Summary

In this section, we went through the differences between animating styles and props and how to use `useAnimatedStyle` and `useAnimatedProps`. To sum up:
Expand Down
201 changes: 201 additions & 0 deletions docs/docs-reanimated/docs/guides/animating-svg.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
id: animating-svg
title: Animating SVG
sidebar_label: Animating SVG
---

Reanimated can animate [`react-native-svg`](https://github.com/software-mansion/react-native-svg) components - both their geometry (`cx`, `r`, `d`, `points`, ...) and their appearance (`fill`, `stroke`, `opacity`, ...). You can drive them with [inline props](/docs/fundamentals/animating-styles-and-props#animating-props), the [`useAnimatedProps`](/docs/core/useAnimatedProps) hook, or the modern, declarative [CSS animations](/docs/css-animations/animation-name) and [CSS transitions](/docs/css-transitions/transition-property).

:::info

CSS animations and transitions for SVG are an experimental feature, enabled by default from Reanimated 4.4. You can opt out with the [`EXPERIMENTAL_CSS_ANIMATIONS_FOR_SVG_COMPONENTS`](/docs/guides/feature-flags#experimental_css_animations_for_svg_components) feature flag. Animating SVG with [`useAnimatedProps`](/docs/core/useAnimatedProps) or inline [shared values](/docs/fundamentals/glossary#shared-value) doesn't require it.

:::

## Setup

Install [`react-native-svg`](https://github.com/software-mansion/react-native-svg). SVG components aren't built into Reanimated, so wrap the ones you animate with [`createAnimatedComponent`](/docs/core/createAnimatedComponent):

```tsx
import Animated from 'react-native-reanimated';
import { Circle } from 'react-native-svg';

const AnimatedCircle = Animated.createAnimatedComponent(Circle);
```

## Animating SVG values

SVG attributes like `cx`, `r`, `d`, and `fill` are **component props**, not React Native `style` keys, so they animate through props rather than `style`. There are three ways to drive them:

- **[Inline](/docs/fundamentals/animating-styles-and-props#animating-props)** - pass a [shared value](/docs/fundamentals/glossary#shared-value) straight to the prop: `<AnimatedCircle r={r} />`.
- **[`useAnimatedProps`](/docs/core/useAnimatedProps)** - compute the props in a worklet and hand the result to the `animatedProps` prop.
- **CSS** - keep the SVG props as plain values and add `animationName` or `transitionProperty` (with their settings) to `animatedProps`.

There's one catch with **CSS transitions**: a CSS transition only runs when the prop changes **between renders**. A plain value does that, so the CSS transition runs. An inline [shared value](/docs/fundamentals/glossary#shared-value) doesn't re-render - each `r.value` change updates the `r` prop directly - so the CSS transition never runs:

```tsx
// Plain value: it changes on re-render, which triggers the CSS transition
<AnimatedCircle
r={grown ? 50 : 20}
animatedProps={{ transitionProperty: 'r', transitionDuration: 300 }}
/>;

// Shared value: r.value changes don't trigger the CSS transition - they update the r prop directly
const r = useSharedValue(20);
<AnimatedCircle
r={r}
animatedProps={{ transitionProperty: 'r', transitionDuration: 300 }}
/>;
```

## With useAnimatedProps

Drive an attribute with a [shared value](/docs/fundamentals/glossary#shared-value) through [`useAnimatedProps`](/docs/core/useAnimatedProps):

import SideBySideExample from '@site/src/components/SideBySideExample';
import SvgUseAnimatedProps from '@site/src/examples/svg/SvgUseAnimatedProps';
import SvgUseAnimatedPropsSrc from '!!raw-loader!@site/src/examples/svg/SvgUseAnimatedProps';

<SideBySideExample src={SvgUseAnimatedPropsSrc} component={SvgUseAnimatedProps} showLines={[14, 41]}/>

## With CSS animations

The same animation expressed as a [CSS keyframe animation](/docs/css-animations/animation-name):

import SvgCssAnimation from '@site/src/examples/svg/SvgCssAnimation';
import SvgCssAnimationSrc from '!!raw-loader!@site/src/examples/svg/SvgCssAnimation';

<SideBySideExample src={SvgCssAnimationSrc} component={SvgCssAnimation} showLines={[11, 28]}/>

## With CSS transitions

A [CSS transition](/docs/css-transitions/transition-property) runs whenever a transitioned value changes between renders - including a plain value from `useState` or props, with no shared value involved. Just change the prop:
Comment thread
MatiPl01 marked this conversation as resolved.

import SvgCssTransition from '@site/src/examples/svg/SvgCssTransition';
import SvgCssTransitionSrc from '!!raw-loader!@site/src/examples/svg/SvgCssTransition';

<SideBySideExample src={SvgCssTransitionSrc} component={SvgCssTransition} showLines={[13, 26]}/>

The first render never animates (there is no previous value); each later change of `r` transitions from the old value to the new one. You can pass the prop inline or through `animatedProps`; changing it from either place starts the transition, and if both set it, `animatedProps` wins:

```tsx
// inline - changing r triggers the transition
<AnimatedCircle
r={grown ? 50 : 20}
animatedProps={{ transitionProperty: 'r', transitionDuration: 300 }}
/>

// through animatedProps - same effect
<AnimatedCircle
animatedProps={{ r: grown ? 50 : 20, transitionProperty: 'r', transitionDuration: 300 }}
/>
```

A [shared value](/docs/fundamentals/glossary#shared-value) is the exception: updating `r.value` doesn't re-render, so it never starts a transition. To animate a prop from a shared value, pass it inline (`r={r}`) or via [`useAnimatedProps`](/docs/core/useAnimatedProps) instead.

## Morphing paths

A `Path` morphs between two shapes when both use the same sequence of commands. Because `d` maps to a real CSS property, this runs on the web as well:

import SvgPathMorph from '@site/src/examples/svg/SvgPathMorph';
import SvgPathMorphSrc from '!!raw-loader!@site/src/examples/svg/SvgPathMorph';

<SideBySideExample src={SvgPathMorphSrc} component={SvgPathMorph} showLines={[7, 37]}/>

## Supported components and properties

The tables below cover what **CSS animations and transitions** can animate, and on which platforms. Every component animates the [common appearance properties](#common-appearance-properties); shape components also animate their [geometry](#geometry-by-component). Properties that aren't listed aren't supported by CSS - use [`useAnimatedProps`](/docs/core/useAnimatedProps) for those, since it can drive any animatable prop the component accepts.

### Common appearance properties

Every `react-native-svg` component supports these props, so they can animate on any of them:

<div className="compatibility fixed">
| Property | Android | iOS | Web |
| ------------------ | ------- | ------ | ------ |
| `color` | <Yes/> | <Yes/> | <Yes/> |
| `fill` | <Yes/> | <Yes/> | <Yes/> |
| `fillOpacity` | <Yes/> | <Yes/> | <Yes/> |
| `fillRule` | <Yes/> | <Yes/> | <Yes/> |
| `stroke` | <Yes/> | <Yes/> | <Yes/> |
| `strokeWidth` | <Yes/> | <Yes/> | <Yes/> |
| `strokeOpacity` | <Yes/> | <Yes/> | <Yes/> |
| `strokeDasharray` | <Yes/> | <Yes/> | <Yes/> |
| `strokeDashoffset` | <Yes/> | <Yes/> | <Yes/> |
| `strokeLinecap` | <Yes/> | <Yes/> | <Yes/> |
| `strokeLinejoin` | <Yes/> | <Yes/> | <Yes/> |
| `vectorEffect` | <Yes/> | <Yes/> | <Yes/> |
| `opacity` | <Yes/> | <Yes/> | <Yes/> |
| `pointerEvents` | <Yes/> | <Yes/> | <Yes/> |
| `clipPath` | <Yes/> | <Yes/> | <No/> |
| `clipRule` | <Yes/> | <Yes/> | <No/> |
| `mask` | <Yes/> | <Yes/> | <No/> |
| `filter` | <Yes/> | <Yes/> | <No/> |
| `marker` | <Yes/> | <Yes/> | <No/> |
</div>

### Geometry by component

These components animate their geometry on iOS and Android. The **Web** column shows whether that geometry animates there too.

| Component | Geometry props | Web |
| --------------------- | ------------------------------------------------------------------- | --- |
| `Circle` | `cx`, `cy`, `r` | ✅ |
| `Ellipse` | `cx`, `cy`, `rx`, `ry` | ✅ |
| `Rect` | `x`, `y`, `width`, `height`, `rx`, `ry` | ✅ |
| `Image` | `x`, `y`, `width`, `height` | ✅ |
| `Path` | `d` | ✅¹ |
| `Polygon`, `Polyline` | `points` | ❌ |
| `Line` | `x1`, `y1`, `x2`, `y2` | ❌ |
| `Text` | `x`, `y`, `dx`, `dy`, `rotate` | ❌ |
| `Pattern` | `x`, `y`, `width`, `height`, `patternUnits`, `patternContentUnits` | ❌ |
| `LinearGradient` | `x1`, `y1`, `x2`, `y2`, `gradient`, `gradientUnits` | ❌ |
| `RadialGradient` | `cx`, `cy`, `r`, `rx`, `ry`, `fx`, `fy`, `gradient`, `gradientUnits` | ❌ |

¹ On Web, `Path` `d` only morphs between paths with matching command structure; mismatches snap. iOS and Android morph freely.

The remaining components - `G`, `Use`, `Symbol`, `Defs`, `ClipPath`, `Mask`, `Marker`, `TSpan`, `TextPath`, and `ForeignObject` - have no animatable geometry; they animate only the [common appearance properties](#common-appearance-properties). On Web, only `G` is supported among them.

`Pattern` `x`/`y` are iOS only - `react-native-svg` doesn't support them on Android, even outside animations. `Text` `x`, `y`, `dx`, `dy`, and `rotate` also accept per-glyph arrays.

## Remarks

### Morphing paths and points

`d` (Path) and `points` (Polygon/Polyline) morph freely between any shapes on iOS and Android. On Web, only `Path` morphs: its `d` interpolates between paths that share the same command structure, and mismatched structures snap. `Polygon` and `Polyline` `points` don't animate on Web at all, because `react-native-svg` renders them as native `<polygon>`/`<polyline>` elements whose `points` is not a CSS property.

### Stepped properties

Some properties step between discrete values rather than interpolating: `strokeLinecap`, `strokeLinejoin`, `fillRule`, `vectorEffect`, `gradientUnits`, and `patternUnits`. This matches native SVG and CSS behavior.

### Units

Geometry props (`cx`, `r`, `x`, `width`, ...) accept plain numbers or percentage strings (`'50%'`), and a single animation can mix the two: an absolute value and a percentage are resolved to the same unit first, so `r` animates smoothly even from `10` to `'50%'`.

Gradient and pattern **coordinates** are the exception: there, a percentage string (`'50%'`) and a `0`-`1` fraction don't interpolate into each other, so a single animation has to use one or the other.

### Gradients

`react-native-svg` defines gradient stops with `<Stop>` children, which can't be animated. To animate them, Reanimated adds a `gradient` prop - an array of `{ offset, color, opacity }` stops that replaces the children. Each stop's `offset`, `color`, and `opacity` can animate, and even the number of stops can differ between `from` and `to`. The gradient's geometry animates too: `x1`/`y1`/`x2`/`y2` for `LinearGradient`, and `cx`/`cy`/`r`/`fx`/`fy`/`rx`/`ry` for `RadialGradient`.

Here, a `RadialGradient` morphs from a two-stop "sun" to a four-stop "sunset". Gradients don't animate on the web, so this is recorded on iOS:

import SvgGradientSrc from '!!raw-loader!@site/src/examples/svg/SvgGradient';

export const gradientVideo = {
light: '/recordings/examples/svg_radial_gradient_light.mp4',
dark: '/recordings/examples/svg_radial_gradient_dark.mp4',
};

<SideBySideExample src={SvgGradientSrc} showLines={[12, 52]} video={gradientVideo}/>

A few caveats:

- You can't mix `<Stop>` children and the `gradient` prop - if both are present, the `gradient` prop wins.
- `gradientUnits` is a [stepped property](#stepped-properties) (it jumps).
- Gradient coordinates can't mix percentage strings and `0`-`1` fractions in one animation (see [Units](#units)).
- Gradients animate on **native platforms only** (not the web). `RadialGradient` `fx`/`fy` animate on iOS only - `react-native-svg` can't apply the focal point on Android.

### Web

On Web, Reanimated drives SVG through CSS, so an attribute animates only if it maps to a real CSS property. Attributes that have no CSS equivalent - `Polygon`/`Polyline` `points`, `Line` endpoints, `Text`/`Pattern`/gradient coordinates, and gradient stops - can't animate via CSS on Web; use [`useAnimatedProps`](/docs/core/useAnimatedProps) for those.
2 changes: 2 additions & 0 deletions docs/docs-reanimated/docs/guides/supported-properties.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ id: supported-properties

Not all CSS properties are available and animatable in React Native. The following table describes which style properties can be animated on which platform.

For animating `react-native-svg` components, see [Animating SVG](/docs/guides/animating-svg).

<div className="compatibility fixed">
| Property | Android | iOS | Web |
| ----------------------- | ------- | ------ | ------ |
Expand Down
4 changes: 2 additions & 2 deletions docs/docs-reanimated/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@
"react-dom": "19.1.1",
"react-native": "0.83.0",
"react-native-gesture-handler": "2.28.0",
"react-native-reanimated": "4.4.1",
"react-native-reanimated": "4.5.0-nightly-20260614-41c1d1a75",
"react-native-svg": "15.15.4",
"react-native-web": "0.21.2",
"react-native-worklets": "0.9.1",
"react-native-worklets": "0.10.0-nightly-20260614-41c1d1a75",
Comment on lines +61 to +64

@MatiPl01 MatiPl01 Jun 17, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Temporary, will be replaced with a valid latest stable version after the release.

Comment on lines 59 to +64
"source-map": "0.7.4",
"source-map-loader": "4.0.1",
"typescript": "5.9.3",
Expand Down
80 changes: 80 additions & 0 deletions docs/docs-reanimated/src/components/SideBySideExample/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import BrowserOnly from '@docusaurus/BrowserOnly';
import useBaseUrl from '@docusaurus/useBaseUrl';
import CollapsibleCode from '@site/src/components/CollapsibleCode';
import ReducedMotionWarning from '@site/src/components/ReducedMotionWarning';
import clsx from 'clsx';
import React from 'react';
import { useReducedMotion } from 'react-native-reanimated';

import styles from './styles.module.css';

type BaseProps = {
/** Raw source of the example, shown in the collapsible code block. */
src: string;
/** Lines initially shown in the code block (0-indexed, inclusive). */
showLines: number[];
};

type PreviewProps = {
component: React.FC;
video?: never;
};

type VideoProps = {
component?: never;
video: { light: string; dark: string };
};

type Props = BaseProps & (PreviewProps | VideoProps);

export default function SideBySideExample({
src,
showLines,
component,
video,
}: Props) {
const Component = component;
const prefersReducedMotion = useReducedMotion();

return (
<div className={styles.row}>
<div className={styles.preview}>
{video ? (
<div className={styles.videoCard}>
<video
className={clsx(styles.video, styles.videoLight)}
autoPlay
loop
muted
playsInline>
<source src={useBaseUrl(video.light)} type="video/mp4" />
</video>
<video
className={clsx(styles.video, styles.videoDark)}
autoPlay
loop
muted
playsInline>
<source src={useBaseUrl(video.dark)} type="video/mp4" />
</video>
</div>
) : (
<div className={styles.liveCard}>
<BrowserOnly
fallback={<div className={styles.loading}>Loading...</div>}>
{() => (
<>
{prefersReducedMotion && <ReducedMotionWarning />}
{Component ? <Component /> : null}
</>
)}
</BrowserOnly>
</div>
)}
</div>
<div className={styles.code}>
<CollapsibleCode src={src} showLines={showLines} />
</div>
</div>
);
}
Loading
Loading