npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leonsilicon/react-native-reanimated-carousel

v0.0.0

Published

Simple carousel component.fully implemented using Reanimated 2.Infinitely scrolling, very smooth.

Readme

react-native-reanimated-carousel

Hacktober Badge platforms npm npm npm github issues github closed issues discord chat

The best carousel component in React Native community. ⚡️

v5 beta notes

  • Sizing: style controls the container size; itemWidth/itemHeight control the page size (snap distance & animation progress).
  • Scroll offset shared value: use scrollOffsetValue (recommended). defaultScrollOffsetValue is deprecated but still supported.
  • Progress: onProgressChange supports both a callback and SharedValue<number>.
  • Pagination accessibility: Pagination.Basic and Pagination.Custom support paginationItemAccessibility for per-item a11y overrides.
  • Custom animation safety: customAnimation styles are sanitized and zIndex is normalized to finite integers.

📊 Version Compatibility

| Carousel Version | Expo SDK | React Native | Reanimated | Gesture Handler | Worklets | |------------------|----------|--------------|------------|-----------------|------------| | v5.0.0-beta | 54+ | 0.80+ | 4.0.0+ | 2.9.0+ | 0.5.0+ | | v4.x | 50-53 | 0.70.3+ | 3.0.0+ | 2.9.0+ | ❌ | | v3.x | 47-49 | 0.66.0+ | 2.0.0+ | 2.0.0+ | ❌ |

Sponsors

License

MIT


Documentation

A complete, in-tree reference for the <Carousel> component, the <Pagination> helpers, and every prop, mode, callback, imperative method, and behavioral nuance.

Table of Contents


Installation

yarn add react-native-reanimated-carousel
# or
npm install react-native-reanimated-carousel

Peer dependencies (see the Version Compatibility table above for exact ranges):

  • react-native-reanimated
  • react-native-gesture-handler
  • react-native-worklets (v5 only)

Configure Reanimated's Babel plugin and wrap your app in <GestureHandlerRootView> per the docs of those libraries. The carousel mounts its own GestureHandlerRootView internally, but you still need the top-level one for gesture handler to work.


Quick Start

import Carousel from "react-native-reanimated-carousel";
import { View, Text, useWindowDimensions } from "react-native";

export function Example() {
  const { width } = useWindowDimensions();

  return (
    <Carousel
      data={["Slide 1", "Slide 2", "Slide 3", "Slide 4"]}
      style={{ width, height: 220 }}
      loop
      pagingEnabled
      renderItem={({ item, index }) => (
        <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
          <Text>{item} (#{index})</Text>
        </View>
      )}
    />
  );
}

Every prop is optional except data and renderItem. The container fills its parent if you omit style (with a dev warning), so for production usage always declare a width and height via style.


Sizing model: container vs. page

The carousel distinguishes container size from page size:

  • Container size is the outer viewport. It comes from style, falling back to deprecated width / height props, and finally to onLayout measurement.
  • Page size is the snap distance and the unit of animation progress (one full page = animation value moving by 1.0). It defaults to the container size — meaning one item fills the viewport — but you can override it with itemWidth / itemHeight to show multiple items at once, or with getItemWidth / getItemHeight for per-item sizes.
// Container 360 px wide, three items visible per page (120 px each)
<Carousel
  style={{ width: 360, height: 200 }}
  itemWidth={120}
  data={data}
  renderItem={renderItem}
/>

Vertical carousels use the same model, swapping widthheight and itemWidthitemHeight:

<Carousel
  vertical
  style={{ width: 240, height: 600 }}
  itemHeight={150}
  data={data}
  renderItem={renderItem}
/>

Variable-size mode (getItemWidth / getItemHeight)

For long lists where every item has a different size — chat feeds, image grids with mixed aspect ratios, masonry-style content — supply getItemWidth(index) (horizontal) or getItemHeight(index) (vertical). The carousel precomputes a prefix-sum offset table before mount, so every visible item is positioned correctly on the first frame with no measurement race, no blank frames, no jump-on-measure.

const WIDTHS = [80, 150, 220, 100, 300, 100, 120, 200, 90, 180];

<Carousel
  data={items}
  style={{ width: screenWidth, height: 200 }}
  getItemWidth={(index) => WIDTHS[index % WIDTHS.length]}
  windowSize={10}
  renderItem={renderItem}
/>

Requirements & caveats

  • The function must return a positive, finite number for every index in [0, data.length). Non-finite or non-positive values are clamped to 0 (and warned about in __DEV__).
  • Treat the function identity as part of the data identity. Memoize with useCallback or hoist to module scope — re-creating it on every render triggers a full prefix-sum rebuild. (O(n) on the JS thread; fast for thousands of items but worth avoiding.)
  • onLayout measurements never override getItem* values. Your declarations are authoritative. If your real content is bigger than declared, it overflows; if smaller, you get trailing whitespace inside the item slot.
  • pagingEnabled snaps by item index — one swipe advances one item, regardless of width. Per-snap pixel distance therefore varies.
  • autoFillData is disabled in variable-size mode. autoFillData duplicates short data arrays assuming uniform size; with variable sizes the heuristic doesn't apply. Pre-duplicate your data array manually if you need a short loopable list.
  • loop is fully supported. Items wrap around using the total span of all declared sizes.
  • Lookup cost. Each visible item performs a binary search through the prefix table on every gesture frame (≈ log₂(N) comparisons per item). For lists with hundreds of items this is negligible; for tens of thousands the cached-hint optimization keeps amortized cost at O(1) during continuous scrolling.

When to use each sizing prop

| Prop | Use when | | --- | --- | | style={{ width, height }} only | One item per page, uniform size | | itemWidth / itemHeight | Multiple items per page, all the same size | | getItemWidth / getItemHeight | Per-item sizes are known up-front |


Animation modes

The mode prop selects a built-in animation strategy. All modes preserve the "no blanks" rendering guarantee — items render synchronously on the UI thread.

normal (default)

Pure horizontal/vertical scroll. Each item translates by the page size as it moves through the viewport.

<Carousel data={data} style={{ width: 300, height: 200 }} renderItem={renderItem} />

parallax

Items scale down and translate inward as they leave the center, creating a depth effect.

<Carousel
  mode="parallax"
  modeConfig={{
    parallaxScrollingOffset: 50,
    parallaxScrollingScale: 0.9,
    parallaxAdjacentItemScale: 0.75,
  }}
  data={data}
  style={{ width: 300, height: 200 }}
  renderItem={renderItem}
/>

modeConfig options (all optional):

  • parallaxScrollingOffset (default 100): pixels each adjacent item is inset by.
  • parallaxScrollingScale (default 0.8): scale of the centered item.
  • parallaxAdjacentItemScale (default parallaxScrollingScale ** 2): scale of the off-center items.

horizontal-stack / vertical-stack

Tinder-style card stack. Items pile up on top of each other and animate off the stack as the user swipes.

<Carousel
  mode="horizontal-stack"
  modeConfig={{
    snapDirection: "left",  // or "right"
    showLength: 3,           // number of cards visible in the stack
    stackInterval: 18,
    scaleInterval: 0.04,
    opacityInterval: 0.1,
    rotateZDeg: 30,
    moveSize: screenWidth,
  }}
  data={data}
  style={{ width: 300, height: 400 }}
  renderItem={renderItem}
/>

Use vertical-stack with vertical flag for vertical card stacks.

customAnimation

Skip the built-in modes and define your own animation as a worklet. Receives the animation value (–1 = one item to the left of center, 0 = centered, 1 = one to the right) and the item index, returns a React Native ViewStyle.

import type { ViewStyle } from "react-native";
import { interpolate } from "react-native-reanimated";

const customAnimation = (value: number, index: number): ViewStyle => {
  "worklet";
  const translateX = interpolate(value, [-1, 0, 1], [-300, 0, 300]);
  const opacity = interpolate(value, [-1, 0, 1], [0.5, 1, 0.5]);
  return {
    transform: [{ translateX }],
    opacity,
  };
};

<Carousel customAnimation={customAnimation} data={data} renderItem={renderItem} />

The carousel sanitizes the returned style on every frame: non-finite zIndex values are clamped to integers, and unsupported keys are dropped.


Loop, paging, and snapping

These three props together describe the carousel's scroll behavior. They're independent:

| Prop | Default | Effect | | --- | --- | --- | | loop | true | When true, scrolling past the last item wraps to the first. When false, the carousel stops at the boundaries. | | pagingEnabled | true | When true, releasing a swipe always snaps to the next or previous item boundary (one swipe = one item). When false, the carousel scrolls freely. | | snapEnabled | true | When true and pagingEnabled is false, releasing a swipe snaps to the nearest item boundary. When both are false, the carousel comes to rest wherever the gesture decay ends. |

Tweaking snap behavior

  • maxScrollDistancePerSwipe — maximum pixel distance a single swipe can travel. Useful for preventing flicks from scrolling past several items at once.
  • minScrollDistancePerSwipe — minimum pixel distance for a swipe to register. Below this threshold, the carousel resets to the current item.
  • scrollAnimationDuration (default 500ms) — how long the spring/timing animation takes when snapping.
  • withAnimation — replace the default ease-out-quart timing with a custom timing or spring config. Takes precedence over scrollAnimationDuration.
<Carousel
  withAnimation={{
    type: "spring",
    config: { damping: 13, mass: 1.2, stiffness: 100 },
  }}
  data={data}
  renderItem={renderItem}
/>

fixedDirection

Forces every swipe to move in a single direction regardless of which way the user drags. Useful for "always scroll forward" patterns.

<Carousel fixedDirection="positive" data={data} renderItem={renderItem} />

Auto-play

<Carousel
  autoPlay
  autoPlayInterval={2500}   // ms between slides (default 1000)
  autoPlayReverse={false}    // when true, plays backwards
  data={data}
  renderItem={renderItem}
/>

Auto-play pauses on touch and resumes on release. It also pauses if enabled={false} is set.


Gestures and overscroll

  • enabled (default true) — when false, the carousel ignores all gestures. Programmatic control (ref.scrollTo, next, prev) still works.
  • overscrollEnabled (default true) — when false, scrolling stops exactly at the edges in non-loop mode. When true, you can drag past the edge with rubber-band feel.
  • onConfigurePanGesture — receives the underlying PanGesture so you can compose with other gesture handlers, set activeOffsetX/activeOffsetY, or chain .simultaneousWithExternalGesture(...).
import { Gesture } from "react-native-gesture-handler";

<Carousel
  onConfigurePanGesture={(pan) => {
    pan.activeOffsetX([-10, 10]);
  }}
  data={data}
  renderItem={renderItem}
/>

Programmatic control (ref)

Attach a ref<ICarouselInstance> to drive the carousel from code.

import { useRef } from "react";
import Carousel, { type ICarouselInstance } from "react-native-reanimated-carousel";

const ref = useRef<ICarouselInstance>(null);

<Carousel ref={ref} data={data} renderItem={renderItem} />;

// Imperative methods:
ref.current?.next();                                    // advance by 1
ref.current?.next({ count: 3, animated: true });        // advance by 3
ref.current?.prev({ count: 2 });                        // go back 2
ref.current?.scrollTo({ index: 0, animated: true });   // jump to index 0
ref.current?.scrollTo({ count: -5 });                   // equivalent to prev(5)
ref.current?.getCurrentIndex();                         // returns the current item index

TCarouselActionOptions

| Field | Type | Description | | --- | --- | --- | | index | number | Absolute target index. Takes precedence over count. | | count | number | Relative offset (positive = forward, negative = backward). | | animated | boolean | Whether to animate the scroll. Defaults to true for next/prev, false for scrollTo. | | onFinished | () => void | Called when the animation settles (or immediately if animated: false). |


Tracking progress

Three callbacks fire during scrolling:

<Carousel
  onScrollStart={() => console.log("scroll began")}
  onScrollEnd={(index) => console.log("settled at", index)}
  onSnapToItem={(index) => console.log("snapped to", index)}
  onProgressChange={(offsetProgress, absoluteProgress) => {
    // offsetProgress: total pixel offset from start (negative when scrolling forward)
    // absoluteProgress: fractional item index (e.g. 1.5 = halfway between items 1 and 2)
  }}
  data={data}
  renderItem={renderItem}
/>

onProgressChange with a SharedValue

For tight integration with Reanimated worklets, pass a SharedValue<number> directly. The carousel writes absoluteProgress into it on every frame without ever crossing the JS thread:

import { useSharedValue } from "react-native-reanimated";

const progress = useSharedValue(0);

<Carousel onProgressChange={progress} data={data} renderItem={renderItem} />;

This is the recommended pattern for driving a <Pagination> indicator (see below).

scrollOffsetValue (advanced)

Pass your own SharedValue<number> as the carousel's internal translation state. The carousel mutates it during gestures and animations, letting you observe or write to the scroll offset from outside the component. Use this when you need to synchronize the carousel with another animated component (custom indicator, parallax background, etc.).

The legacy defaultScrollOffsetValue prop is still accepted but deprecated; prefer scrollOffsetValue.


<Pagination> indicators

Two flavors, both driven by a SharedValue<number> (typically wired up via onProgressChange).

Pagination.Basic

Simple dots that interpolate width / color between active and inactive states.

import { Pagination } from "react-native-reanimated-carousel";
import { useSharedValue } from "react-native-reanimated";

const progress = useSharedValue(0);

<Carousel onProgressChange={progress} data={data} renderItem={renderItem} />
<Pagination.Basic
  progress={progress}
  data={data}
  horizontal
  size={12}
  dotStyle={{ backgroundColor: "#999", borderRadius: 6 }}
  activeDotStyle={{ backgroundColor: "#000" }}
  containerStyle={{ gap: 8 }}
  onPress={(i) => ref.current?.scrollTo({ index: i, animated: true })}
/>

Pagination.Custom

Same API plus a customReanimatedStyle(progress, index, length) => style worklet so you can implement any indicator shape (bars, segments, fading rings, etc.).

<Pagination.Custom
  progress={progress}
  data={data}
  size={20}
  customReanimatedStyle={(progress, index, length) => {
    "worklet";
    const distance = Math.abs(progress - index);
    return {
      opacity: 1 - Math.min(distance, 1),
      transform: [{ scale: 1 - 0.4 * Math.min(distance, 1) }],
    };
  }}
/>

Accessibility overrides

Both variants accept paginationItemAccessibility(index, length) returning overrides:

<Pagination.Basic
  progress={progress}
  data={data}
  carouselName="Featured photos"
  paginationItemAccessibility={(index, length) => ({
    accessibilityLabel: `Photo ${index + 1} of ${length}`,
    accessibilityRole: "button",
  })}
/>

Without overrides, items announce as "Slide N of M" (or "Slide N of M - <carouselName>" if carouselName is set).


Render-item contract

type CarouselRenderItem<T> = (info: {
  item: T;
  index: number;
  animationValue: SharedValue<number>;
}) => React.ReactElement;
  • item — the data entry.
  • index — the real item index (already corrected for autoFillData duplication).
  • animationValue — a SharedValue<number> representing the item's position relative to the viewport: 0 when centered, -1 when one page to the left, 1 when one page to the right, and so on. Useful for driving per-item animations from inside the rendered subtree.
import Animated, { useAnimatedStyle, interpolate } from "react-native-reanimated";

const renderItem = ({ item, animationValue }) => {
  const style = useAnimatedStyle(() => ({
    opacity: interpolate(animationValue.value, [-1, 0, 1], [0.4, 1, 0.4]),
  }));
  return <Animated.View style={[styles.card, style]}>{/* ... */}</Animated.View>;
};

Complete prop reference

All props on <Carousel> (everything except data and renderItem is optional).

Data & rendering

| Prop | Type | Default | Description | | --- | --- | --- | --- | | data | T[] | required | Items to render. | | renderItem | CarouselRenderItem<T> | required | Function that returns a React element for each item. | | autoFillData | boolean | true | When loop=true and data.length is 1 or 2, duplicates entries internally so the loop animation has enough items. Disabled in variable-size mode. | | defaultIndex | number | 0 | Initial item index. | | keyExtractor | implicit | uses index | Items are keyed by their array index. |

Sizing

| Prop | Type | Default | Description | | --- | --- | --- | --- | | style | StyleProp<ViewStyle> | — | Container style. Provide width/height here. | | contentContainerStyle | StyleProp<ViewStyle> | — | Style for the inner content container. Avoid opacity and transform here — they conflict with internal animations. | | itemWidth | number | container width | Horizontal page size (snap distance). Use to show multiple items at once. | | itemHeight | number | container height | Vertical page size when vertical=true. | | getItemWidth | (i: number) => number | — | Per-item width. Enables variable-size mode. | | getItemHeight | (i: number) => number | — | Per-item height for vertical carousels. | | vertical | boolean | false | When true, items scroll top-to-bottom. | | width | number | — | Deprecated. Use style={{ width }}. | | height | number | — | Deprecated. Use style={{ height }}. |

Scroll behavior

| Prop | Type | Default | Description | | --- | --- | --- | --- | | loop | boolean | true | Wrap-around scrolling. | | pagingEnabled | boolean | true | Snap to one-item-at-a-time on release. | | snapEnabled | boolean | true | When paging is off, still snap to the nearest item. | | overscrollEnabled | boolean | true | Allow dragging past the edges (non-loop mode). | | enabled | boolean | true | Disable all gesture input. | | fixedDirection | "positive" \| "negative" | — | Force every swipe to move in the given direction. | | maxScrollDistancePerSwipe | number | — | Cap on a single swipe's pixel distance. | | minScrollDistancePerSwipe | number | — | Threshold below which a swipe is ignored. | | scrollAnimationDuration | number | 500 | Animation duration for snap, in milliseconds. | | withAnimation | WithSpringAnimation \| WithTimingAnimation | — | Custom animation config; takes precedence over scrollAnimationDuration. | | windowSize | number | data.length | Number of items rendered around the visible window. Lower = better perf with very long lists. |

Auto-play

| Prop | Type | Default | Description | | --- | --- | --- | --- | | autoPlay | boolean | false | Automatically advance. | | autoPlayInterval | number | 1000 | Milliseconds between auto-advances. | | autoPlayReverse | boolean | false | Play backwards. |

Animation modes

| Prop | Type | Description | | --- | --- | --- | | mode | "parallax" \| "horizontal-stack" \| "vertical-stack" | Built-in animation strategy. Omit for the default linear translation. | | modeConfig | ILayoutConfig (parallax) or stack ILayoutConfig | Mode-specific tuning. | | customAnimation | (value: number, index: number) => ViewStyle | Worklet that returns the per-item style. Bypasses built-in modes. | | customConfig | CustomConfig \| (() => CustomConfig) | Override the internal type/viewCount used by useOffsetX. Advanced. |

Callbacks

| Prop | Type | Description | | --- | --- | --- | | onScrollStart | () => void | Fires when a gesture begins or next/prev/scrollTo is called. | | onScrollEnd | (index: number) => void | Fires when the scroll settles. | | onSnapToItem | (index: number) => void | Fires when the carousel snaps to a new item. | | onProgressChange | ((offset: number, abs: number) => void) \| SharedValue<number> | Continuous scroll progress. Pass a SharedValue for zero-overhead worklet integration. | | onConfigurePanGesture | (pan: PanGesture) => void | Customize the underlying Gesture Handler PanGesture. | | onLayout | (e: LayoutChangeEvent) => void | Standard React Native onLayout for the container. |

Shared values

| Prop | Type | Description | | --- | --- | --- | | scrollOffsetValue | SharedValue<number> | External translation state; the carousel mutates this during gestures. | | defaultScrollOffsetValue | SharedValue<number> | Deprecated alias for scrollOffsetValue. |

Misc

| Prop | Type | Description | | --- | --- | --- | | testID | string | E2E test identifier. | | ref | Ref<ICarouselInstance> | Imperative handle. See Programmatic control. |


TypeScript exports

import Carousel, {
  Pagination,
  type TCarouselProps,
  type ICarouselInstance,
  type CarouselRenderItem,
  type IComputedDirectionTypes,
  type TAnimationStyle,
  type ILayoutConfig,
} from "react-native-reanimated-carousel";
  • TCarouselProps<T> — full props shape, generic over data[number].
  • ICarouselInstance — the imperative handle (next, prev, scrollTo, getCurrentIndex).
  • CarouselRenderItem<T> — the renderItem signature.
  • IComputedDirectionTypes — internal helper for the vertical/horizontal prop variants; rarely needed at the call site.
  • TAnimationStyle — the customAnimation worklet signature.
  • ILayoutConfig — the stack mode's modeConfig shape.

Migration notes (v4 → v5)

  • Sizing: move width/height into style. The props still work but emit deprecation warnings.
  • Page size: itemWidth/itemHeight now control snap distance, not container size. To show multiple items at once: keep the container at full width via style={{ width }}, and set itemWidth to the per-page distance.
  • Scroll offset: prefer scrollOffsetValue over the deprecated defaultScrollOffsetValue.
  • Progress: onProgressChange accepts a SharedValue<number> directly. The two-arg callback form still works.
  • Custom animations: styles are now sanitized; non-finite zIndex is normalized. If you were relying on unsupported style keys, they'll be silently dropped.
  • Pagination accessibility: both Pagination.Basic and Pagination.Custom now accept paginationItemAccessibility and carouselName props for screen-reader customization.
  • Variable-size mode (new in this release): if you previously emulated variable widths via customAnimation, you can now use getItemWidth / getItemHeight for a fully integrated solution that preserves snap behavior, looping, and progress tracking.

Performance notes

  • Synchronous rendering: every visible item's position is a pure function of handlerOffset evaluated on the UI thread. The carousel doesn't await measurement before placing items, which is what makes scrolling jank-free even on slow devices.
  • windowSize: for lists longer than ~20 items, set windowSize to a small number (e.g., 5–10) so off-screen items are not even constructed.
  • autoFillData: only kicks in for data.length 1 or 2 when loop=true. For longer arrays it's a no-op; you don't need to disable it.
  • Variable-size lookups: O(log n) per visible item per frame, with a cached-hint fast path for continuous scrolling that amortizes to O(1). For lists in the tens of thousands, this is still well below 1 ms per frame.
  • customAnimation: every worklet runs on every frame for every visible item. Keep them simple — prefer interpolate over hand-rolled math, and avoid allocating arrays/objects inside the worklet.

Architecture deep dive

For contributors and curious users who want to understand the internals — the hooks ecosystem, the math behind position interpolation, the gesture state machine, and how the SizeResolver abstraction makes variable-size mode work — see docs/ARCHITECTURE.md.