Skip to content
Native Animation
CardsGestureSwipeDecisionUndo

Decision Swipe Deck

A physics-driven decision deck with gesture-reactive stamps, accessible controls, and undo.

$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 decision-swipe-deck

About this animation

A physics-driven decision deck with gesture-reactive stamps, accessible controls, and undo.

How it works

  1. The component measures its container and sizes the cards, fan, stamps, and controls proportionally.
  2. One horizontal shared value drives the active card, decision tint, stamp reveal, and the progress that advances every card behind it.
  3. Release distance and horizontal velocity jointly decide whether the card returns or exits; the exit spring preserves the gesture velocity.
  4. The initial touch height reverses the rotation direction, so the card turns naturally around the point where it was grabbed.
  5. Completed decisions are kept in a small history stack. undo() remounts the previous card on the side where it exited and springs it home.

Why this one

Most swipe decks stop at a card flying left or right. This version communicates the decision while the finger moves, prepares the next card continuously, accounts for throw velocity and grip position, and includes both undo and a complete touch-accessible alternative to the gesture.

Install

npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets

Usage

import DecisionSwipeDeck from './DecisionSwipeDeck';

export function Choices() {
  return (
    <DecisionSwipeDeck
      items={options}
      keyExtractor={(item) => item.id}
      renderCard={(item) => <OptionCard option={item} />}
      onSwipe={(item, direction) => saveDecision(item.id, direction)}
    />
  );
}

Customize labels, the threshold, visible stack, and imperative controls:

import { useRef } from 'react';
import DecisionSwipeDeck, { type DecisionSwipeDeckRef } from './DecisionSwipeDeck';

export function EventPicker() {
  const deckRef = useRef<DecisionSwipeDeckRef>(null);

  return (
    <DecisionSwipeDeck
      ref={deckRef}
      items={events}
      keyExtractor={(event) => event.id}
      renderCard={(event) => <EventPoster event={event} />}
      yesLabel="SAVE"
      noLabel="PASS"
      threshold={0.24}
      visibleCount={4}
      fontFamily="YourSansFont"
      onEmpty={() => showSummary()}
    />
  );
}

The forwarded ref exposes swipe('left' | 'right') and undo().

Props

PropTypeDefaultDescription
itemsT[]RequiredItems shown in deck order.
renderCard(item: T, index: number) => ReactNodeRequiredRenders the contents of a card.
keyExtractor(item: T) => stringRequiredReturns a stable key for an item.
yesLabelstring'YES'Positive stamp and button label.
noLabelstring'NOPE'Negative stamp and button label.
thresholdnumber0.28Release threshold as a fraction of container width.
visibleCountnumber3Maximum number of mounted cards.
onSwipe(item: T, direction: 'left' | 'right') => void—Called after a card exits.
onEmpty() => void—Called when the last card exits.
showButtonsbooleantrueShows the No, Undo, and Yes controls.
fontFamilystringSystem fontFont used by decision stamps.
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, not yet verified
  • Works in Expo Go because its native dependencies are included in the SDK

Accessibility

The three circular controls are labeled buttons and provide a full alternative to dragging. The active card is exposed as an adjustable decision card with a gesture hint. When reduced motion is enabled, decisions and undo update immediately without flying or spring movement.