---
title: "Custom Animations"
description: "Build custom item animations with logical relative progress"
canonical_url: "https://rn-carousel.dev/custom-animations"
markdown_url: "https://rn-carousel.dev/custom-animations.md"
version: "5"
---
# Custom Animations

v5 has two complementary animation surfaces:

- `itemAnimation` controls the outer style of every slide;
- render-item `relativeProgress` lets content inside a slide animate independently.

Both are UI-thread/worklet surfaces. `layout` and `itemAnimation` are mutually exclusive.

## `itemAnimation`

```tsx
import { interpolate } from "react-native-reanimated";
import {
  Carousel,
  type CarouselItemAnimation,
} from "react-native-reanimated-carousel";

const itemAnimation: CarouselItemAnimation = (relativeProgress) => {
  "worklet";

  return {
    opacity: interpolate(relativeProgress, [-1, 0, 1], [0.4, 1, 0.4]),
    transform: [
      {
        scale: interpolate(relativeProgress, [-1, 0, 1], [0.85, 1, 0.85]),
      },
    ],
  };
};

<Carousel
  data={data}
  renderItem={({ item }) => <Slide item={item} />}
  itemAnimation={itemAnimation}
/>;
```

The callback signature is:

```ts
type CarouselItemAnimation = (
  relativeProgress: number,
  index: number
) => ViewStyle;
```

It must be worklet-compatible. Avoid JS-only APIs, React state, and side effects inside it.

## Relative progress semantics

For each item:

- `0` means selected;
- a positive value means logically forward;
- a negative value means logically backward;
- fractions represent movement between items;
- values are not clamped;
- loop mode uses the nearest equivalent copy and remains continuous.

The sign is the same for horizontal, vertical, LTR, and RTL carousels. This makes opacity, scale, and logical sequencing portable across directions.

## Animating content inside a slide

`renderItem` receives a `SharedValue`:

```tsx
import Animated, {
  interpolate,
  useAnimatedStyle,
} from "react-native-reanimated";
import type { SharedValue } from "react-native-reanimated";

function AnimatedCaption({
  relativeProgress,
  title,
}: {
  relativeProgress: SharedValue<number>;
  title: string;
}) {
  const style = useAnimatedStyle(() => ({
    opacity: interpolate(
      Math.abs(relativeProgress.value),
      [0, 1],
      [1, 0]
    ),
    transform: [
      {
        translateY: interpolate(
          relativeProgress.value,
          [-1, 0, 1],
          [24, 0, -24]
        ),
      },
    ],
  }));

  return <Animated.Text style={style}>{title}</Animated.Text>;
}

<Carousel
  data={data}
  renderItem={({ item, relativeProgress }) => (
    <AnimatedCaption title={item.title} relativeProgress={relativeProgress} />
  )}
/>;
```

Do not read `relativeProgress.value` during React render. Read it from a Reanimated worklet such as `useAnimatedStyle` or `useDerivedValue`.

## Horizontal RTL

Logical progress is normalized, but React Native transforms are physical. If a custom effect moves items on the x-axis, map its sign for RTL:

```tsx
import { I18nManager } from "react-native";
import { interpolate } from "react-native-reanimated";
import type { CarouselItemAnimation } from "react-native-reanimated-carousel";

const rtlSign = I18nManager.isRTL ? -1 : 1;

const itemAnimation: CarouselItemAnimation = (relativeProgress) => {
  "worklet";

  const logicalX = interpolate(
    relativeProgress,
    [-1, 0, 1],
    [-80, 0, 80]
  );

  return {
    transform: [{ translateX: logicalX * rtlSign }],
  };
};
```

Built-in normal, parallax, and horizontal-stack layouts perform this mapping internally. Do not reverse `data` or mirror the whole carousel with `scaleX`.

Vertical effects do not depend on RTL.

## Animation sanitization

Carousel sanitizes the returned style before applying it:

- invalid or non-finite style values are removed;
- `zIndex` is normalized to a finite integer;
- item dimensions remain owned by Carousel.

Use finite interpolation output and keep expensive calculations outside the callback when they can be precomputed.

## Transition animation

`itemAnimation` describes slide appearance. The separate `animation` prop describes how the carousel moves to a settled position:

```tsx
<Carousel
  data={data}
  renderItem={renderItem}
  itemAnimation={itemAnimation}
  animation={{ type: "timing", duration: 350 }}
/>
```

The transition configuration applies to gesture snapping, imperative commands, and autoplay. For a spring:

```tsx
<Carousel
  data={data}
  renderItem={renderItem}
  itemAnimation={itemAnimation}
  animation={{
    type: "spring",
    damping: 18,
    stiffness: 180,
  }}
/>
```

Reanimated applies the operating system's reduce-motion preference by default.

## Built-in layout or custom animation

Use a built-in `layout` when it covers the effect:

```tsx
<Carousel
  data={data}
  renderItem={renderItem}
  layout={{ type: "parallax", offset: 80, scale: 0.9 }}
/>
```

Use `itemAnimation` when the slide style needs a custom mapping. Passing both is a TypeScript error and a runtime error for untyped JavaScript.
