Colorful UI cards gliding between drop zones on a phone-shaped workbench with a relaxed red Maine Coon cat in the background.

React Native Reanimated DnD: Drag, Drop, and Sort at 60fps

The Orange Cat
The Orange Cat

If you have ever tried to add drag-and-drop to a React Native app, you probably remember the stutter. The dragged item lags a frame behind your finger, the list jumps as it re-renders, and the whole interaction feels just slightly broken. The root cause is almost always the same: gesture math and animation are being routed through the JavaScript thread, which is busy doing everything else. React Native Reanimated DnD takes a different approach. It runs the drag gestures and animations as worklets on the UI thread, so dragging stays buttery smooth even when JS is slammed.

This is not a port of a web library like react-dnd or dnd-kit. It was designed for mobile from the ground up, built on top of Reanimated 4, Gesture Handler, and the new Worklets runtime. It gives you both low-level primitives (Draggable and Droppable with real collision detection) and high-level containers (Sortable lists and SortableGrid), so a kanban-ish drop zone, a reorderable todo list, and a home-screen-style icon grid all come from the same toolkit.

What Makes It Worth a Look

The headline feature is the UI-thread architecture, but the breadth of the API is what makes it stick:

  • 60fps dragging driven by Reanimated 4 worklets, decoupled from JS-thread work.
  • Smart collision detection with three algorithms: center, intersect, and contain.
  • Free-form drag-and-drop via Draggable and Droppable primitives.
  • Sortable lists, vertical and horizontal, FlatList-optimized, with dynamic and variable item heights for expand/collapse layouts.
  • SortableGrid for 2D reordering, with both insert and swap modes.
  • Drag handles, so only a specific grab region starts a drag.
  • Bounded and axis-locked dragging to keep items inside a container or on a single axis.
  • Nine-point drop alignment plus offsets, drop-zone capacity limits, and custom animation functions.
  • Full TypeScript types, a small (~70 KB) tree-shakeable bundle, and a clean lifecycle state machine.

Before You Install: The Fine Print

This is the one section to read carefully, because version 2 has hard requirements. The library is New Architecture only (Fabric), since Reanimated 4 requires it. You will need Expo SDK 55 or newer, or React Native 0.83+ as the tested target (the peer dependency allows RN 0.80 and up). You also need three peer dependencies installed and version-aligned.

npm install react-native-reanimated-dnd \
  react-native-reanimated \
  react-native-gesture-handler \
  react-native-worklets
yarn add react-native-reanimated-dnd \
  react-native-reanimated \
  react-native-gesture-handler \
  react-native-worklets

Two things trip people up the most. First, Reanimated 4 split its worklets runtime into the separate react-native-worklets package, so it must be installed explicitly. Second, the react-native-worklets/plugin Babel plugin must be the last entry in your babel.config.js, or your worklets will silently stop working. Finally, your app root must be wrapped in a GestureHandlerRootView.

Your First Drop Zone

Let's start with the building blocks. Three pieces matter: DropProvider is a context that coordinates every draggable and droppable on screen, Draggable makes any view movable, and Droppable is a target that fires a callback when something lands on it.

import { GestureHandlerRootView } from "react-native-gesture-handler";
import { View, Text } from "react-native";
import { Draggable, Droppable, DropProvider } from "react-native-reanimated-dnd";

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <DropProvider>
        <Droppable onDrop={(data) => console.log("Dropped:", data)}>
          <View style={styles.dropZone}>
            <Text>Drop here</Text>
          </View>
        </Droppable>

        <Draggable data={{ id: "1", title: "Drag me!" }}>
          <View style={styles.item}>
            <Text>Drag me around!</Text>
          </View>
        </Draggable>
      </DropProvider>
    </GestureHandlerRootView>
  );
}

The data prop on a Draggable is whatever payload you want, and that same object is handed back to the droppable's onDrop. The provider runs collision detection between the live position of the dragged item and every registered drop zone, then fires the matching callback. No manual coordinate bookkeeping required.

Reorderable Lists With a Handle

Free dragging is great, but most apps want a sortable list. The Sortable component wraps a FlatList-style renderer and handles all the reordering animation internally. Pair it with SortableItem and the nested SortableItem.Handle so dragging only begins when the user grabs a specific grip, not the whole row.

import { useCallback } from "react";
import { View, Text } from "react-native";
import {
  Sortable,
  SortableItem,
  SortableRenderItemProps,
} from "react-native-reanimated-dnd";

const tasks = [
  { id: "1", title: "Learn React Native" },
  { id: "2", title: "Build an app" },
  { id: "3", title: "Deploy to store" },
];

function TaskList() {
  const renderItem = useCallback(
    (props: SortableRenderItemProps<(typeof tasks)[0]>) => {
      const { item, id, ...rest } = props;
      return (
        <SortableItem key={id} id={id} data={item} {...rest}>
          <View style={styles.task}>
            <Text>{item.title}</Text>
            <SortableItem.Handle>
              <Text>{"⋮⋮"}</Text>
            </SortableItem.Handle>
          </View>
        </SortableItem>
      );
    },
    []
  );

  return <Sortable data={tasks} renderItem={renderItem} itemHeight={60} />;
}

One hard rule worth tattooing on your wrist: every item in a sortable or grid data array must have a string id. Numeric or missing ids are invalid, and the library will warn you at runtime in development. The handle support in v2 uses an explicit registration pattern rather than tree-walking, which makes it considerably more reliable than it was in the v1 line.

Home-Screen Grids

New in version 2, SortableGrid brings true 2D reordering, the kind of interaction you see when rearranging app icons. You describe the grid geometry through a dimensions object and the library figures out the rest.

import { useCallback } from "react";
import { View, Text } from "react-native";
import { SortableGrid, SortableGridItem } from "react-native-reanimated-dnd";

function AppGrid() {
  const renderItem = useCallback((props) => {
    const { item, id, ...rest } = props;
    return (
      <SortableGridItem key={id} id={id} {...rest}>
        <View style={styles.gridItem}>
          <Text>{item.label}</Text>
        </View>
      </SortableGridItem>
    );
  }, []);

  return (
    <SortableGrid
      data={apps}
      renderItem={renderItem}
      dimensions={{
        columns: 4,
        itemWidth: 80,
        itemHeight: 80,
        rowGap: 12,
        columnGap: 12,
      }}
    />
  );
}

The grid supports both insert mode, where dropping an item shifts everything else over to make room, and swap mode, where two items trade places. If you need to drive a fully custom layout, the package also exports its grid calculation utilities along with useGridSortable and useGridSortableList hooks.

Fine-Tuning the Drag

When you reach for the low-level Draggable, a handful of props give you precise control over how the gesture behaves. You can pick the collision algorithm, lock movement to a single axis, constrain the draggable within a bounding container, and supply your own animation function for the snap-back.

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

<Draggable
  data={item}
  collisionAlgorithm="intersect"   // "center" | "intersect" | "contain"
  dragAxis="y"
  dragBoundsRef={containerRef}
  animationFunction={(toValue) => withSpring(toValue)}
  onStateChange={(state) => console.log(state)} // IDLE / DRAGGING / DROPPED
>
  <View style={styles.card}>
    <Text>Constrained, vertical-only, springy</Text>
  </View>
</Draggable>

The three collision algorithms answer slightly different questions. center checks whether the dragged item's center point is inside the drop zone, intersect triggers on any overlap, and contain requires the item to be fully inside the zone. Combine these with the dropAlignment (a nine-point grid from top-left to bottom-right) and dropOffset props on the droppable, and you can control exactly where an item snaps when it lands. Droppables also accept a capacity to limit how many items they hold and an activeStyle to highlight themselves while a draggable hovers over them.

Watching the Lifecycle

Every draggable moves through a small state machine: IDLE, DRAGGING, and DROPPED. You can subscribe to the transitions and the live motion through a set of callbacks, which is handy for analytics, haptics, or syncing some lightweight UI elsewhere on the screen.

import { DraggableState } from "react-native-reanimated-dnd";

<Draggable
  data={item}
  preDragDelay={150}
  onDragStart={(data) => console.log("started", data)}
  onDragging={({ x, y, tx, ty }) => {
    // x/y are the origin, tx/ty are the live translation
  }}
  onStateChange={(state: DraggableState) => {
    if (state === DraggableState.DROPPED) {
      // celebrate
    }
  }}
  onDragEnd={(data) => console.log("ended", data)}
>
  <View style={styles.card}>
    <Text>Tracked</Text>
  </View>
</Draggable>

The preDragDelay prop, also new in v2, is a small but practical addition: it adds a short hold threshold so the library can tell a tap apart from the start of a drag, which matters a lot when your draggable is also a tappable button.

One important discipline to keep in mind: do not mutate your external list state during a drag. The sortable and grid components own their reordering state internally so the animation stays consistent, so callbacks like onDragging and the allPositions payload on onDrop are meant for read-only tracking, logging, and analytics. Calling setItems or splicing your array mid-drag will fight the component. Programmatic list operations from outside a drag are on the roadmap rather than shipped today.

Where It Fits

The honest pitch is that this library shines when you need more than list reordering. If all you want is a vertical sortable list, react-native-draggable-flatlist is more battle-tested for that single job. But if you want free dragging into named drop zones, collision detection, sortable lists, and reorderable grids from one consistent and TypeScript-first API, this is the broadest modern option going. It is also a genuinely up-to-date alternative to the long-unmaintained react-native-drax.

The trade-off is the New Architecture requirement. If your project is still on the Old Architecture or Reanimated 3, version 2 is off the table until you migrate. And as a roughly one-year-old library with a small team, a few ambitious roadmap items like nested sortables and a built-in Kanban board are not done yet. But for greenfield apps already moving to Fabric and Reanimated 4, React Native Reanimated DnD delivers on its tagline: drag-and-drop that finally feels right on React Native.