Skip to content
Native Animation
CarouselAccordionPhotosGestureInteractive

Expanding Strip Carousel

A photo carousel where one generous card and a row of narrow strips continuously trade space during a drag.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI.

npx nativeanimation add expanding-strip-carousel

About this animation

A photo carousel where one generous card and a row of narrow strips continuously trade space during a drag.

Why this one

Most expanding accordions jump directly between fixed layouts. This version distributes width continuously under the finger, supports either axis, and waits until a card has enough room before sliding and fading its copy into view. Any number of items can be supplied, so the number of visible strips is controlled by the data.

How it works

  1. The component measures its own container and calculates the active weight required to give the selected strip the requested share of useful space.
  2. One continuous position shared value drives every strip's weight, offset, size, image parallax, copy reveal, and indicator state.
  3. A pan moves that value in proportion to twice the resting narrow-strip size, then velocity and proximity choose the spring target.
  4. A tap hit-tests the live strip geometry, so selection remains accurate midway through an interrupted transition.
  5. Copy stays hidden until its strip passes half of its final active size, then enters with opacity and translation.

Install

npx expo install expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets

Usage

import ExpandingStripCarousel from './ExpandingStripCarousel';

export function Places() {
  return (
    <ExpandingStripCarousel
      items={[
        { image: require('./forest.jpg'), title: 'Fern Trail', subtitle: 'North Ridge' },
        { image: require('./coast.jpg'), title: 'Quiet Coast', subtitle: 'West Bay' },
      ]}
    />
  );
}

Customize its axis and proportions, or select a card from outside:

import { useRef } from 'react';
import ExpandingStripCarousel, { type ExpandingStripCarouselRef } from './ExpandingStripCarousel';

export function VerticalPlaces() {
  const ref = useRef<ExpandingStripCarouselRef>(null);

  return (
    <ExpandingStripCarousel
      ref={ref}
      items={places}
      direction="vertical"
      activeShare={0.72}
      gap={8}
      radius={28}
      height={0.7}
      fontFamily="YourSansFont"
      titleFontFamily="YourDisplayFont"
      onIndexChange={(index) => trackSelection(index)}
    />
  );
}

The forwarded ref exposes select(index).

Props

PropTypeDefaultDescription
itemsStripItem[]RequiredPhotos, titles, optional subtitles, and optional accent colors.
direction'horizontal' | 'vertical''horizontal'Axis along which strips divide the available space.
activeSharenumber0.66Resting share for the active strip, clamped from 0.5 to 0.85.
gapnumber6Space between strips in points.
radiusnumber22Strip corner radius in points.
heightnumber0.42Strip-stage height as a fraction of component height.
fontFamilystringSystem fontFont for subtitles.
titleFontFamilystringSystem fontFont for strip and footer titles.
onIndexChange(index: number) => void—Called when selection changes.
styleStyleProp<ViewStyle>—Outer-container style.
testIDstring—Test identifier for the outer container.

Compatibility

  • Expo SDK 57
  • React Native 0.86
  • Reanimated 4.5 and Worklets 0.10
  • iOS: supported, not yet verified on a device
  • Android: supported, not yet verified on a device
  • Web: supported, pending visual verification
  • Works in Expo Go because its native dependencies are included in the SDK

Accessibility

The carousel is exposed as an adjustable control with its active title, place, position, and increment/decrement actions. Tapping a strip provides an alternative to dragging. With reduced motion enabled, taps, accessibility actions, and external selections update immediately without a spring.