--- url: /guide/getting-started/overview.md --- # Overview ![react-native-swipe-deck preview](https://raw.githubusercontent.com/react-native-motion-kit/react-native-swipe-deck/main/assets/react-native-swipe-deck-preview.webp) `@react-native-motion-kit/swipe-deck` is a high-performance Tinder-style swipe deck for React Native. It is built for apps that need a smooth card stack, programmatic like/pass buttons, progress-driven overlays, and a typed API that does not make every screen wire a controller by hand. ## Mental Model The main API is a typed compound component family: - `createSwipeDeck()` creates one deck family for an item type. - `Root` owns data, gesture state, motion options, actions, events, and layout. - `Card` renders each mounted item in the bounded forward window. - Factory hooks read state, run actions, subscribe to events, and expose Reanimated shared values for animated UI. ```tsx import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; type Profile = { id: string; name: string; }; const ProfileDeck = createSwipeDeck(); ``` Create the factory once, export it from a shared module, and use hooks from that same factory around a matching `Root`. ## Why It Exists - The deck does not render the full data set. - Promoted cards keep item identity through stable keys. - Manual drag motion and programmatic action motion can be tuned separately. - Interaction shared values update on the UI thread without rerendering React every gesture frame. - Events describe committed model changes such as swipes, undo, index changes, and end reached. ## Core Concepts ### Bounded Forward Window By default, the deck mounts up to three cards from the active index forward: 1. current card 2. next card 3. next buffered card This keeps the stack continuous without backfilling dismissed cards. Use `visibleCardCount` to tune that maximum budget. ### Stable Item Keys `getKey` is required. The same logical item must return the same key across swipes, and different items should not share a key. The deck uses that key as the React identity for each mounted card. ### Committed Events vs Live Interaction Use event hooks for committed model changes. Use interaction shared values for frame-synchronous visual feedback while a card is dragging, dismissing, or restoring. --- url: /guide/getting-started/installation.md --- # Installation Install the package and its required React Native gesture/animation peer dependencies: ```sh [npm] npm @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets ``` ```sh [yarn] yarn @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets ``` ```sh [pnpm] pnpm @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets ``` ```sh [bun] bun @react-native-motion-kit/swipe-deck react-native-gesture-handler react-native-reanimated react-native-worklets ``` ```sh [deno] deno @react-native-motion-kit/swipe-deck npm:react-native-gesture-handler npm:react-native-reanimated npm:react-native-worklets ``` ## Minimum Versions | Package | Minimum | | ------------------------------ | -------- | | `react` | `18.0.0` | | `react-native` | `0.75.0` | | `react-native-gesture-handler` | `2.24.0` | | `react-native-reanimated` | `4.0.0` | | `react-native-worklets` | `0.5.0` | ## React Native Setup Follow the official setup guides for your React Native or Expo version: - [React Native Reanimated getting started](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) - [React Native Gesture Handler installation](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation) In Babel config, make sure `react-native-worklets/plugin` is the last Babel plugin. ```js title="babel.config.js" module.exports = { presets: ['module:@react-native/babel-preset'], plugins: ['react-native-worklets/plugin'], }; ``` Gesture Handler also expects your app surface to be under `GestureHandlerRootView`. ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; export function AppRoot() { return {/* app */}; } ``` --- url: /guide/getting-started/quick-start.md --- # Quick Start Create one typed deck family for your item type. `Root`, `Card`, hooks, actions, and events from that factory share the same registry namespace. ## Examples & Demo - [📁 Example Project](https://github.com/react-native-motion-kit/react-native-swipe-deck/tree/main/example) - Real implementation code with various use cases. - [🤖 Expo Snack](https://snack.expo.dev/@harang/react-native-swipe-deck) - Try it instantly on Expo Snack. ```tsx import { Text, View } from 'react-native'; import { createSwipeDeck, SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; type Profile = { id: string; name: string; bio: string; }; const ProfileDeck = createSwipeDeck({ motion: SwipeDeckMotion.tinder({ drag: { mode: 'horizontal', liftYFactor: 0.15, }, rotation: { mode: 'grab-position', }, dismiss: { threshold: ({ width }) => width * 0.3, velocityThreshold: 800, minDuration: 300, maxDuration: 520, }, }), }); function ProfileDeckEvents() { ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { console.log(item.name, direction, source); }); ProfileDeck.useDeckEventListener('endReached', () => { console.log('No more cards'); }); return null; } function ProfileCard({ profile, active }: { profile: Profile; active: boolean }) { return ( {active ? 'Now viewing' : 'Up next'} {profile.name} {profile.bio} ); } export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { return ( <> item.id} visibleCardCount={3}> {({ item, isActive }) => } ); } ``` ## Add Controls Use state and action hooks for React-rendered UI such as counters and buttons. ```tsx function ProfileDeckControls() { const { activeIndex, count, canSwipe, canUndo, isCompleted } = ProfileDeck.useDeckState(); const { swipeLeft, swipeRight, swipeUp, undo } = ProfileDeck.useDeckActions(); const current = activeIndex >= 0 ? activeIndex + 1 : 0; return ( {isCompleted ? 'Done' : `${current} / ${count}`} Nope Undo Like Up ); } ``` Add `allowedDirections={['left', 'right', 'up']}` on `Root` to enable the up action. Upward gestures also require `drag.mode: 'free'`; the horizontal configuration above intentionally keeps vertical finger movement disabled. Enable undo tracking only when you expose undo UX: ```tsx item.id} undoEnabled> {({ item }) => } ``` --- url: /guide/getting-started/ai.md --- # AI Usage Guide When generating code with an AI assistant, give it these rules. They prevent the most common integration mistakes. ## LLM-Readable Docs This site publishes Markdown files for AI tools during `rspress build`. - [`/llms.txt`](/llms.txt): compact documentation index with page links and summaries. Use this when the agent should choose only the relevant pages. - [`/llms-full.txt`](/llms-full.txt): full English documentation in one Markdown file. Use this when the agent has enough context budget and should understand the whole API surface. Public URLs: - [`https://react-native-swipe-deck.pages.dev/llms.txt`](https://react-native-swipe-deck.pages.dev/llms.txt) - [`https://react-native-swipe-deck.pages.dev/llms-full.txt`](https://react-native-swipe-deck.pages.dev/llms-full.txt) ## Recommended Prompt ```md Use @react-native-motion-kit/swipe-deck. - Create one createSwipeDeck() factory outside render. - Render Root and Card from that same factory. - Use a stable getKey(item) value, usually item.id. - Use factory hooks from the same factory instance as the Root. - Use useDeckState/useDeckActions for React UI. - Use useDeckInteraction for Reanimated UI-thread overlays. - Use useDeckEvent/useDeckEventListener for committed swipe, undo, indexChange, and endReached events. - Add undoEnabled only when the UI exposes undo. - Do not derive deck id from item ids, timestamps, or values that change each render. ``` ## Good Factory Shape ```tsx // profile-deck.ts import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; export type Profile = { id: string; name: string; }; export const ProfileDeck = createSwipeDeck(); ``` ```tsx // ProfileDeckScreen.tsx import { ProfileDeck } from './profile-deck'; export function ProfileDeckScreen({ profiles }: { profiles: Profile[] }) { return ( item.id}> {({ item }) => } ); } ``` ## Common Mistakes To Avoid - Do not call `createSwipeDeck()` inside a component render path. - Do not use hooks from one factory to control a Root from another factory. - Do not use array indexes as keys when items can be inserted, removed, or reordered. - Do not use `useDeckInteraction` for committed business logic; use events for committed model changes. - Do not turn on `undoEnabled` unless the deck exposes undo controls. --- url: /guide/usage/basic-usage.md --- # Basic Usage ## Factory API The factory API is the primary API. Use it when a deck needs hooks, actions, events, interaction shared values, or multiple named instances. ```tsx import { createSwipeDeck } from '@react-native-motion-kit/swipe-deck'; const ProfileDeck = createSwipeDeck(); function Screen() { return ( item.id}> {({ item }) => } ); } ``` The factory keeps `Root`, `Card`, and hooks on the same item type without repeating generics in JSX. ## Card Render Info `Card` receives render info for each mounted item. | Field | Meaning | | ---------- | ------------------------------------------------------- | | `item` | The item from your `data` array. | | `index` | The item index in `data`. | | `offset` | The card offset from the active item. | | `role` | `'current'` for the active card, `'next'` for buffered. | | `isActive` | `true` only for the active card. | ```tsx {({ item, role, isActive }) => } ``` ## Interactive Card Content Cards are non-interactive by default so the deck can own swipe gestures safely. Use `interactive` when the active card contains touchable children such as CTA buttons or links. ```tsx {({ item }) => openAd(item)} />} ``` Use the predicate form when only some items should expose touchable children. ```tsx item.type === 'ad'}> {({ item }) => } ``` `interactive` only affects the active card. Background cards remain non-interactive. For deeper nested gestures such as card-local carousels or scroll views, prefer a dedicated future gesture-coordination API instead of exposing raw `pointerEvents`. ## Allowed Directions `allowedDirections` limits which directions can be accepted as a dismiss. ```tsx item.id} allowedDirections={['left', 'right', 'up']} > {({ item }) => } ``` - Omit `allowedDirections` to allow left and right. - Include `'up'` explicitly to allow upward dismisses. - Gesture up requires `SwipeDeckMotion.tinder({ drag: { mode: 'free' } })`, the default. - `drag.mode: 'horizontal'` rejects gesture up but does not block `actions.swipeUp()`. - Pass `[]` to allow dragging but reject every dismiss release and programmatic swipe action. ## Static API Use the static API only when you need inline card rendering and do not need factory hooks. ```tsx import { SwipeDeck } from '@react-native-motion-kit/swipe-deck'; function InlineDeck() { return ( item.id}> >{({ item }) => } ); } ``` Static `Root` does not accept `id`, and static `Root` / `Card` do not share a factory type. Pass the item type to `Card` when you want typed render props. --- url: /guide/usage/deck-hooks.md --- # Deck Hooks Factory hooks expose deck state, actions, and interaction values without a provider prop or controller object. ```tsx const ProfileDeck = createSwipeDeck(); ``` ## `useDeckState(id?)` Returns React-rendered deck state. | Field | Meaning | | ------------- | --------------------------------------------------------- | | `activeIndex` | Current active item index, or `-1` before attach. | | `count` | Total item count in the attached deck. | | `isCompleted` | Whether the deck has consumed all items. | | `canSwipe` | Whether a dismiss action can currently be accepted. | | `canUndo` | Whether the latest valid swipe can currently be restored. | ```tsx function Counter() { const { activeIndex, count, isCompleted } = ProfileDeck.useDeckState(); const current = activeIndex >= 0 ? activeIndex + 1 : 0; return {isCompleted ? 'Done' : `${current} / ${count}`}; } ``` Derive the current item from your own `data[activeIndex]` when needed. Deck state stays primitive and stable. ## `useDeckActions(id?)` Returns stable action callbacks. ```tsx function Controls() { const { canSwipe, canUndo } = ProfileDeck.useDeckState(); const { swipeLeft, swipeRight, swipeUp, undo } = ProfileDeck.useDeckActions(); return ( Nope Undo Like Up ); } ``` `swipeLeft()`, `swipeRight()`, and `swipeUp()` return `true` when accepted and `false` when the deck is unattached, disabled, animating, unmeasured, completed, or the direction is not allowed. `swipeUp()` requires `allowedDirections` to include `'up'`, but it is not blocked by horizontal drag mode. `undo()` returns `true` when `canUndo` is true, including after the deck is completed. ## `useDeckInteraction(id?)` Returns Reanimated shared values for progress-driven UI. ```tsx function SwipeReactionOverlay() { const { intentDirection, progress, signedProgress } = ProfileDeck.useDeckInteraction(); const likeStyle = useAnimatedStyle(() => { const progress = Math.max(signedProgress.get(), 0); return { opacity: progress, transform: [{ scale: 0.9 + progress * 0.18 }], }; }); const upStyle = useAnimatedStyle(() => { const upProgress = intentDirection.get() === 'up' ? progress.get() : 0; return { opacity: upProgress, transform: [{ scale: 0.9 + upProgress * 0.18 }], }; }); return ( LIKE SUPER LIKE ); } ``` Interaction values update on the UI thread and do not rerender React every gesture frame. | Value | Meaning | | ------------------ | ----------------------------------------------------------------------------- | | `progress` | Dominant enabled-axis progress from `0` to `1`. | | `signedProgress` | Horizontal signed progress from `-1` to `1`; up is `0`. | | `direction` | Horizontal live signal: `-1`, `0`, or `1`; up is `0`. | | `intentDirection` | Policy-filtered live semantic intent: `'left'`, `'right'`, `'up'`, or `null`. | | `dismissDirection` | Accepted dismiss side: `'left'`, `'right'`, `'up'`, or `null`. | | `translationX` | Active card horizontal translation. | | `translationY` | Active card vertical translation. | | `isDragging` | Whether the deck is dragging or dismissing. | | `phase` | `idle`, `dragging`, `dismissing`, or `undoing`. | Use `intentDirection` for one-at-a-time live reaction overlays. It updates on each drag frame, stays set to an accepted direction while dismissing, and resets with the rest of the interaction values. Use `phase` for frame-synchronous visual feedback. Use event hooks for committed state changes. --- url: /guide/usage/handling-events.md --- # Handling Events Event hooks describe committed model events. They are separate from live interaction values. ## Event Map | Event | Payload | | ------------- | ------------------------------------ | | `swipe` | `{ item, index, direction, source }` | | `undo` | `{ item, index, direction }` | | `indexChange` | `{ index }` | | `endReached` | `true` | `swipe.source` is `'gesture'` when the user releases a pan gesture past the configured threshold or velocity policy. It is `'programmatic'` when the swipe commits through `actions.swipeLeft()`, `actions.swipeRight()`, or `actions.swipeUp()`. ## `useDeckEvent` `useDeckEvent(eventName, initialValue?, id?)` returns the latest committed event snapshot for React-rendered UI. ```tsx function ProfileDeckStatus() { const lastSwipe = ProfileDeck.useDeckEvent('swipe', null); const endReached = ProfileDeck.useDeckEvent('endReached', false); return ( {endReached ? 'Done' : lastSwipe ? `Last swipe: ${lastSwipe.direction} from ${lastSwipe.source}` : 'No swipe yet'} ); } ``` This is a latest-value API, not an event history. Event snapshots clear when a Root attaches or detaches. Initial values are intentionally restricted to the event payload shape, `null`, `undefined`, or `false` for `endReached`. Use `null` for object events such as `swipe`. For a named deck without an initial value, pass the id as the second argument: ```tsx const lastSwipe = ProfileDeck.useDeckEvent('swipe', 'nearby'); ``` If you also pass an initial value, id is the third argument. ## `useDeckEventListener` `useDeckEventListener(eventName, listener, id?)` subscribes imperatively without forcing React state into your app code. ```tsx function ProfileDeckEvents() { ProfileDeck.useDeckEventListener('swipe', ({ item, direction, source }) => { console.log(item, direction, source); }); ProfileDeck.useDeckEventListener('undo', ({ item }) => { console.log('Restored', item); }); ProfileDeck.useDeckEventListener('endReached', () => { console.log('No more cards'); }); return null; } ``` Successful swipe events emit in `swipe -> indexChange -> endReached` order. Undo emits `undo -> indexChange`. --- url: /guide/usage/programmatic-actions.md --- # Programmatic Actions Programmatic actions come from `useDeckActions()`. Use them for buttons, external controls, or any app logic that should dismiss the active card. ```tsx function LikeButton() { const { swipeRight, swipeUp } = ProfileDeck.useDeckActions(); return ( <> Like Up ); } ``` `swipeUp()` is accepted only when the Root explicitly includes `'up'` in `allowedDirections`. Gesture up also requires free drag mode; programmatic `swipeUp()` is still available when drag mode is horizontal. ## Action Motion `motion` controls manual drag feel. `actionMotion` controls only programmatic actions such as `swipeLeft()`, `swipeRight()`, and `swipeUp()`. ```tsx import { createSwipeDeck, SwipeDeckActionMotion, SwipeDeckMotion, } from '@react-native-motion-kit/swipe-deck'; const ProfileDeck = createSwipeDeck({ motion: SwipeDeckMotion.tinder(), actionMotion: SwipeDeckActionMotion.springboard({ anticipationDistance: ({ width }) => width * 0.04, anticipationDuration: 80, dismissDuration: 320, }), }); ``` ## Recipes ### `SwipeDeckActionMotion.direct(options?)` Dismisses immediately toward the action direction. Omitted values reuse the deck's resolved dismiss duration, easing, and offscreen multiplier. ```tsx actions.swipeLeft( SwipeDeckActionMotion.direct({ duration: 180, }), ); ``` ### `SwipeDeckActionMotion.springboard(options?)` Moves a little in the opposite direction first, then dismisses offscreen. During anticipation, swipe progress and live direction stay neutral so opposite-side overlays do not flash. ```tsx const actionMotion = SwipeDeckActionMotion.springboard({ anticipationDistance: 40, anticipationDuration: 160, dismissDuration: 500, }); ``` ## Precedence `actionMotion` is replacement-based, not deep-merged: 1. factory `actionMotion` from `createSwipeDeck({ actionMotion })` 2. `Root actionMotion`, replacing the factory default for that Root 3. per-call recipe passed to `swipeLeft(recipe)`, `swipeRight(recipe)`, or `swipeUp(recipe)` Actions are callback-safe. If a React Native press event is passed to `swipeRight`, `swipeLeft`, or `swipeUp`, the event argument is ignored and the configured action motion is used. --- url: /guide/usage/undo.md --- # Undo Undo is opt-in. Add `undoEnabled` to a Root when the deck exposes undo or back-swipe UX. ```tsx item.id} undoEnabled> {({ item }) => } ``` When enabled, each successful swipe stores one key/index/direction metadata entry in a LIFO undo stack. Lookups use a key-to-index map for the current `data`, and invalid entries are pruned when data or keys change. When omitted, successful swipes do not store undo metadata, `canUndo` stays `false`, and `actions.undo()` returns `false`. ## Undo Button ```tsx function UndoButton() { const { canUndo } = ProfileDeck.useDeckState(); const { undo } = ProfileDeck.useDeckActions(); return ( Undo ); } ``` ## Undo Motion Use `SwipeDeckUndoMotion` to customize how a restored card enters. ```tsx import { createSwipeDeck, SwipeDeckUndoMotion } from '@react-native-motion-kit/swipe-deck'; const ProfileDeck = createSwipeDeck({ undoMotion: SwipeDeckUndoMotion.spring({ springConfig: { damping: 36, stiffness: 300, mass: 3, }, }), }); ``` Available recipes: - `SwipeDeckUndoMotion.spring(options?)`: restores with Reanimated `withSpring`. - `SwipeDeckUndoMotion.timing(options?)`: restores with deterministic `withTiming`. Both recipes accept: - `from: 'auto' | 'left' | 'right'`: `auto` returns from the original swipe direction. - `entryDistance`: number or layout callback for the offscreen start distance. `timing` also accepts `duration` and `easing`. Its default duration is `0`, so the card is restored immediately unless you opt into custom motion. `spring` accepts `springConfig`. ## Precedence `undoMotion` is replacement-based: 1. factory `undoMotion` from `createSwipeDeck({ undoMotion })` 2. `Root undoMotion`, replacing the factory default for that Root 3. per-call recipe passed to `actions.undo(recipe)` Undo is callback-safe. If a React Native press event is passed to `undo`, the event argument is ignored. --- url: /guide/usage/motion.md --- # Motion `SwipeDeckMotion.tinder()` is the built-in motion preset for Tinder-style card stacks. ```tsx import { SwipeDeckMotion } from '@react-native-motion-kit/swipe-deck'; const motion = SwipeDeckMotion.tinder({ drag: { mode: 'horizontal', liftYFactor: 0.15, }, rotation: { mode: 'grab-position', }, dismiss: { threshold: ({ width }) => width * 0.3, velocityThreshold: 800, minDuration: 300, maxDuration: 520, }, }); ``` ## What Follows Swipe Progress? Buffered next cards animate with swipe progress: - scale moves toward `1` - opacity moves toward `1` - `translateY` moves toward `0` When a swipe commits, the dismissed card exits offscreen, the promoted next card keeps its item identity, and a new future item enters the bounded window. ## Drag Mode `drag.mode` controls how the active card uses finger translation while dragging. | Mode | Active card movement | | ------------ | ---------------------------------------------------------- | | `free` | Follows both horizontal and vertical finger movement. | | `horizontal` | Ignores vertical finger movement and uses horizontal drag. | `drag.liftYFactor` lifts the active card upward by `abs(translationX) * liftYFactor`. ## Rotation `rotation.mode` controls whether the rotation anchor is fixed or resolved from the gesture start position. ### Fixed Rotation Use fixed rotation when every gesture should use the same anchor. | Origin | Feel | | --------------- | ------------------------------- | | `center` | Rotates around the card center. | | `top-center` | The upper edge feels anchored. | | `bottom-center` | The lower edge feels anchored. | ```tsx SwipeDeckMotion.tinder({ rotation: { mode: 'fixed', origin: 'bottom-center', direction: 'default', }, }); ``` Use `direction: 'reverse'` when you want the same fixed anchor with the opposite rotation sign. ### Grab-Position Rotation Grab-position rotation is the default Tinder-like behavior. ```tsx SwipeDeckMotion.tinder({ rotation: { mode: 'grab-position', direction: 'default', maxDegrees: 25, }, }); ``` Upper-half grabs use a top-center anchor with the default rotation sign. Lower-half grabs use a bottom-center anchor with the reverse rotation sign. `direction: 'reverse'` flips that mapping. ## Dismiss Target `dismiss.offscreenMultiplier` controls the successful swipe release target. - Successful swipes always dismiss offscreen. - The default `1.5` sends the card to `clearDistance * 1.5`. - `clearDistance` is resolved at release from the swipe direction, rotation mode, rotation direction, and gesture start position. - Values below `1` normalize to `1`. - If `duration` is omitted, velocity-derived timing is computed from the remaining distance to this target. Most apps can tune only `threshold`, `velocityThreshold`, `duration`, `minDuration`, `maxDuration`, and `easing`. ## Motion Precedence Motion values resolve in this order: 1. factory motion defaults from `createSwipeDeck({ motion })` 2. `Root motion`, partially overriding only the fields it specifies 3. direct root props such as `swipeThreshold` and `velocityThreshold` Factory and Root motion are deep-merged. Changing `rotation.mode` does not reset numeric rotation tuning such as `maxDegrees` or `inputRange`. ## Preset Stability Keep motion presets stable with a module-scope constant or `useMemo`. ```tsx const profileDeckMotion = SwipeDeckMotion.tinder({ rotation: { mode: 'fixed', origin: 'bottom-center', }, dismiss: { threshold: ({ width }) => width * 0.3, }, }); const ProfileDeck = createSwipeDeck({ motion: profileDeckMotion, }); ``` --- url: /guide/usage/multi-instance-management.md --- # Multi-Instance Management Use `id` only when you render multiple roots from the same factory. ```tsx function MultiDeckScreen() { const nearbyState = ProfileDeck.useDeckState('nearby'); return ( <> item.id}> {({ item }) => } item.id}> {({ item }) => } {nearbyState.activeIndex + 1} ); } ``` ## Id Rules ::: info Use a stable id per mounted deck `id` is a factory-scoped deck namespace, not an item key. Different factories can safely use the same id, but simultaneous Roots from one factory must use distinct ids. A stable navigation route key is supported as long as it does not change while the screen is mounted. ::: - Use screen-level names such as `"nearby"` or `"recommended"`, or a navigation route key that remains stable for one mounted screen. - Give simultaneous Roots from the same factory distinct ids. The duplicate-Root rule is unchanged: two mounted Roots from the same factory and same id are invalid. - Do not derive ids from item ids, timestamps, values that change per render, or values that change while the screen is mounted. - Create factories outside render paths, and keep each id stable for the mounted lifecycle. The registry keeps hooks, actions, and interaction shared values stable while at least one committed Root or public hook consumer retains that id. After the final committed consumer cleans up and the registry finishes its deferred eviction, a later consumer for the same id receives fresh actions and interaction shared values. Never-committed abandoned renders are a known best-effort cleanup limitation: if a render reads a brand-new id but React never commits it, there is no committed cleanup path for the library to observe. Avoid creating ids from values that change while a screen is mounted. ## Same-Factory Rule Hooks, actions, and interactions only connect to Roots created by the same factory instance. ```ts const ProfileDeck = createSwipeDeck(); export const { Root: ProfileDeckRoot, Card: ProfileDeckCard, useDeckState: useProfileDeckState, useDeckActions: useProfileDeckActions, useDeckInteraction: useProfileDeckInteraction, } = ProfileDeck; ``` Calling `createSwipeDeck()` again creates a separate registry namespace even when the item type and id are the same. --- url: /guide/usage/visible-card-budget.md --- # Visible Card Budget `visibleCardCount` controls the maximum number of cards kept mounted from the active card forward. ```tsx function CurrentOnlyDeck() { return ( item.id} visibleCardCount={1}> {/* current card only */} ); } function DefaultDeck() { return ( item.id} visibleCardCount={3}> {/* default budget for smoother next-card promotion */} ); } function DeepStackDeck() { return ( item.id} visibleCardCount={9}> {/* deeper stacked UI */} ); } ``` ## Rules | Input | Mounted cards | | ----------------------------------------------- | ---------------------------------------- | | `visibleCardCount={1}` | Only the current card. | | `visibleCardCount={2}` | Up to two cards when enough data exists. | | `visibleCardCount={20}` with 10 remaining items | At most those 10 remaining items. | | Even values | Kept as the maximum budget. | Values below `1` normalize to `1` when enough forward data exists. The mounted count never exceeds the remaining data from the active index. Keep the budget as small as your UI allows. Increase it only when your design visibly exposes a deeper stack or intentionally needs more cards pre-mounted. --- url: /guide/usage/api-reference.md --- # API Reference ## Main Exports ```ts export { SwipeDeckActionMotion, SwipeDeckMotion, createSwipeDeck, SwipeDeck, SwipeDeckUndoMotion, } from '@react-native-motion-kit/swipe-deck'; ``` [Go to source](https://github.com/react-native-motion-kit/react-native-swipe-deck/blob/main/src/index.tsx) ## `createSwipeDeck(config?)` Creates a typed deck family. ```tsx const ProfileDeck = createSwipeDeck({ motion: SwipeDeckMotion.tinder(), }); ``` The returned instance includes: - `Root` - `Card` - `useDeckState` - `useDeckActions` - `useDeckInteraction` - `useDeckEvent` - `useDeckEventListener` ## `SwipeDeck.Root` Props [Go to source](https://github.com/react-native-motion-kit/react-native-swipe-deck/blob/main/src/types.ts) | Prop | Type | Notes | | ------------------- | ------------------------------------ | ----------------------------------------- | | `id` | `string` | Factory-scoped deck namespace. | | `data` | `readonly T[]` | Ordered items rendered by the deck. | | `getKey` | `(item: T, index: number) => string` | Required stable item key. | | `initialIndex` | `number` | Initial active item index. | | `disabled` | `boolean` | Disables accepted actions and gestures. | | `allowedDirections` | `AllowedDirections` | Limits accepted dismiss directions. | | `swipeThreshold` | `SwipeThreshold` | Root-level dismiss threshold override. | | `velocityThreshold` | `number` | Root-level flick threshold override. | | `motion` | `SwipeDeckMotionPreset` | Gesture-driven motion preset. | | `actionMotion` | `SwipeDeckActionMotionRecipe` | Programmatic swipe motion recipe. | | `undoMotion` | `SwipeDeckUndoMotionRecipe` | Programmatic undo restore motion recipe. | | `undoEnabled` | `boolean` | Enables undo history tracking. | | `visibleCardCount` | `number` | Maximum mounted forward card budget. | | `containerStyle` | `StyleProp` | Style applied to the deck container. | | `children` | `ReactNode` | Must include a matching `SwipeDeck.Card`. | ```ts type AllowedDirections = readonly ('left' | 'right' | 'up')[]; type SwipeThreshold = number | ((layout: SwipeDeckLayout) => number); ``` Static `SwipeDeck.Root` accepts the same props except `id`. See [Multi-Instance Management](/guide/usage/multi-instance-management.md) for the complete id contract. `id` is not an item key. Use a stable deck namespace such as `"nearby"`, `"recommended"`, or a navigation route key that does not change while the screen is mounted. Identity is stable while at least one committed Root or public hook consumer remains mounted for that id. After the final cleanup and deferred eviction, a later consumer receives fresh actions and interaction shared values. Do not derive ids from item ids, timestamps, or values that change while mounted. Two simultaneous Roots from the same factory still require distinct ids. ## `SwipeDeck.Card` Props | Prop | Type | Notes | | ------------- | ---------------------------------------------------- | --------------------------------------------------- | | `interactive` | `boolean \| ((info: SwipeRenderInfo) => boolean)` | Enables touchable children on the active card only. | | `style` | `StyleProp` | Style applied to the absolute card. | | `children` | `(info: SwipeRenderInfo) => ReactNode` | Renders one item in the bounded window. | `SwipeRenderInfo` includes `item`, `index`, `offset`, `role`, and `isActive`. ## State And Actions ```ts type SwipeDeckState = { activeIndex: number; count: number; isCompleted: boolean; canSwipe: boolean; canUndo: boolean; }; type SwipeDeckActions = { swipeLeft: SwipeDeckAction; swipeRight: SwipeDeckAction; swipeUp: SwipeDeckAction; undo: SwipeDeckUndoAction; }; ``` Programmatic actions return `boolean` to tell whether the action was accepted. ## Interaction ```ts type SwipeDeckInteractionPhase = 'idle' | 'dragging' | 'dismissing' | 'undoing'; ``` `SwipeDirection` is `'left' | 'right' | 'up'`. Omitted `allowedDirections` allows only left and right; upward dismisses are opt-in. `useDeckInteraction()` returns Reanimated shared values for `progress`, `signedProgress`, `direction`, `intentDirection`, `dismissDirection`, `translationX`, `translationY`, `isDragging`, and `phase`. `intentDirection` is the policy-filtered live semantic source for overlays: `'left'`, `'right'`, `'up'`, or `null`. Only centered upward gestures enter the internal up cone; ordinary upper-left and upper-right diagonals remain horizontal. If that raw horizontal winner is not allowed, `intentDirection`, `progress`, and `signedProgress` stay neutral while card translation still follows the finger. `signedProgress` and numeric `direction` remain horizontal-only and are `0` for upward intent or dismiss. `dismissDirection` can be `'left'`, `'right'`, `'up'`, or `null` after a dismiss is accepted. ## Events ```ts type SwipeDeckEventMap = { swipe: SwipeEvent; undo: UndoEvent; indexChange: { index: number }; endReached: true; }; ``` Use `useDeckEvent` for latest event snapshots and `useDeckEventListener` for imperative subscriptions. ## Motion Helpers - `SwipeDeckMotion.tinder(options?)` - `SwipeDeckActionMotion.direct(options?)` - `SwipeDeckActionMotion.springboard(options?)` - `SwipeDeckUndoMotion.spring(options?)` - `SwipeDeckUndoMotion.timing(options?)` For behavior details, prefer the guide pages over treating this reference as an onboarding surface. --- url: /index.md --- # React Native Swipe Deck Tinder-style swipe decks for React Native > High-performance card stacks powered by Reanimated, Worklets, and Gesture Handler [Quick Start](/guide/getting-started/quick-start.html) | [GitHub](https://github.com/react-native-motion-kit/react-native-swipe-deck) ## Features - 🤌 **Gesture-First Deck UX**: Drag, flick, threshold, and direction controls tuned for Tinder-style card stacks. - đŸŽī¸ **High-Performance Animations**: Smooth card motion powered by React Native Reanimated shared values and worklets. - đŸĒŸ **Bounded Render Window**: Mount only the active card and a small forward stack instead of the whole data set. - đŸ§Ŧ **Item-Stable Promotion**: Stable item keys let promoted cards keep their React Native view identity. - 🧠 **Typed Compound API**: Create one typed deck family with Root, Card, hooks, actions, and events. - đŸŽ›ī¸ **External Control API**: Trigger swipeLeft, swipeRight, swipeUp, and undo from buttons or other UI components. - 🎨 **Motion Recipes**: Tune gesture motion, programmatic actions, and undo restores independently. - 🧩 **Multi-Instance Management**: Manage multiple deck roots independently with stable factory-scoped IDs. - â†Šī¸ **Undo Support**: Opt into back-swipe UX with action-safe undo motion and LIFO history. - đŸĒ„ **Easy-to-Use API**: Start with Root and Card, then add hooks only when controls or events need them.