React Native Reanimated DnDReact Native Reanimated DnD
Core Components

Draggable

The Draggable component makes any React Native component draggable with smooth animations, collision detection, and advanced features like handles, constraints, and custom animations.

Overview

The Draggable component wraps your content and enables drag-and-drop functionality. It supports various features like collision detection, bounded dragging, axis constraints, custom animations, and drag handles for precise interaction control.

Basic Usage

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

function MyDraggable() {
  return (
    <Draggable data={{ id: "1", name: "Item 1", type: "task" }}>
      <View style={styles.item}>
        <Text>Drag me!</Text>
      </View>
    </Draggable>
  );
}

Props Reference

Core Props

PropTypeDefaultDescription
dataTDataRequiredData payload passed to drop handlers
childrenReactNodeRequiredContent to render inside the draggable
draggableIdstringauto-generatedUnique identifier for the draggable
styleStyleProp<ViewStyle>-Style for the container

Interaction Props

PropTypeDefaultDescription
dragDisabledbooleanfalseWhether dragging is disabled
onDragStart(data: TData) => void-Callback when dragging starts
onDragEnd(data: TData) => void-Callback when dragging ends
onDragging(payload) => void-Callback during dragging with position info
onStateChange(state) => void-Callback when draggable state changes

Advanced Props

PropTypeDefaultDescription
animationFunctionAnimationFunctionspringCustom animation function
dragBoundsRefRefObject<View>-Bounds for constraining dragging
dragAxis'x' | 'y' | 'both''both'Axis constraints for movement
collisionAlgorithmCollisionAlgorithm'intersect'Collision detection method

Collision Algorithms

Choose the right collision detection algorithm for your use case:

intersect (Default)

Best for general use cases and easy dropping:

<Draggable
  data={data}
  collisionAlgorithm="intersect" // Any overlap triggers collision
>
  <TaskCard />
</Draggable>

center

Best for precise positioning and slot-based interfaces:

<Draggable
  data={data}
  collisionAlgorithm="center" // Center point must be over droppable
>
  <GamePiece />
</Draggable>

contain

Best for strict containment requirements:

<Draggable
  data={data}
  collisionAlgorithm="contain" // Entire item must be within droppable
>
  <FileIcon />
</Draggable>

Examples

Basic Draggable with Callbacks

function TaskCard({ task }) {
  return (
    <Draggable
      data={task}
      onDragStart={(data) => {
        console.log("Started dragging:", data.title);
        // Show global drag feedback
        setDraggedItem(data);
        hapticFeedback();
      }}
      onDragEnd={(data) => {
        console.log("Finished dragging:", data.title);
        // Hide global drag feedback
        setDraggedItem(null);
      }}
      onDragging={({ x, y, tx, ty, itemData }) => {
        const currentX = x + tx;
        const currentY = y + ty;
        console.log(`${itemData.title} at (${currentX}, ${currentY})`);

        // Update cursor position or proximity indicators
        updateDragPosition(currentX, currentY);
      }}
      onStateChange={(state) => {
        if (state === DraggableState.DROPPED) {
          showSuccessAnimation();
        }
      }}
    >
      <View style={styles.taskCard}>
        <Text style={styles.title}>{task.title}</Text>
        <Text style={styles.priority}>{task.priority}</Text>
      </View>
    </Draggable>
  );
}

Constrained Dragging

function BoundedSlider() {
  const boundsRef = useRef(null);

  return (
    <View ref={boundsRef} style={styles.sliderContainer}>
      <Text>Horizontal Slider</Text>

      <Draggable
        data={{ value: 50 }}
        dragBoundsRef={boundsRef}
        dragAxis="x" // Only horizontal movement
        collisionAlgorithm="center"
        animationFunction={(toValue) => {
          "worklet";
          return withTiming(toValue, { duration: 200 });
        }}
      >
        <View style={styles.sliderThumb}>
          <Text>●</Text>
        </View>
      </Draggable>
    </View>
  );
}

const styles = StyleSheet.create({
  sliderContainer: {
    height: 60,
    backgroundColor: "#f0f0f0",
    borderRadius: 30,
    justifyContent: "center",
    paddingHorizontal: 20,
  },
  sliderThumb: {
    width: 40,
    height: 40,
    backgroundColor: "#007AFF",
    borderRadius: 20,
    justifyContent: "center",
    alignItems: "center",
  },
});

Draggable with Handle

function CardWithHandle({ card }) {
  const [dragState, setDragState] = useState(DraggableState.IDLE);

  return (
    <Draggable
      data={card}
      onStateChange={setDragState}
      style={[
        styles.card,
        dragState === DraggableState.DRAGGING && styles.dragging,
      ]}
    >
      <View style={styles.cardContent}>
        <Text style={styles.cardTitle}>{card.title}</Text>
        <Text style={styles.cardDescription}>{card.description}</Text>

        {/* Only this handle can initiate dragging */}
        <Draggable.Handle style={styles.dragHandle}>
          <View style={styles.handleIcon}>
            <Text style={styles.handleText}>⋮⋮</Text>
          </View>
        </Draggable.Handle>
      </View>
    </Draggable>
  );
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: "white",
    borderRadius: 8,
    padding: 16,
    margin: 8,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  dragging: {
    shadowOpacity: 0.3,
    shadowRadius: 8,
    elevation: 8,
    transform: [{ scale: 1.05 }],
  },
  cardContent: {
    flexDirection: "row",
    alignItems: "center",
  },
  cardTitle: {
    fontSize: 16,
    fontWeight: "bold",
    flex: 1,
  },
  cardDescription: {
    fontSize: 14,
    color: "#666",
    flex: 2,
  },
  dragHandle: {
    padding: 8,
    marginLeft: 8,
  },
  handleIcon: {
    width: 20,
    height: 20,
    justifyContent: "center",
    alignItems: "center",
  },
  handleText: {
    fontSize: 16,
    color: "#999",
    fontWeight: "bold",
  },
});

File Drag and Drop

function FileItem({ file }) {
  const [isDragging, setIsDragging] = useState(false);

  return (
    <Draggable
      data={file}
      collisionAlgorithm="intersect"
      onDragStart={(data) => {
        setIsDragging(true);
        hapticFeedback();
        showDragOverlay(data);
      }}
      onDragEnd={(data) => {
        setIsDragging(false);
        hideDragOverlay();
      }}
      style={[styles.fileItem, isDragging && styles.draggingFile]}
    >
      <View style={styles.fileContent}>
        <Icon
          name={getFileIcon(file.type)}
          size={32}
          color={getFileColor(file.type)}
        />
        <View style={styles.fileInfo}>
          <Text style={styles.fileName}>{file.name}</Text>
          <Text style={styles.fileSize}>{formatFileSize(file.size)}</Text>
        </View>
        {isDragging && (
          <View style={styles.dragIndicator}>
            <Text style={styles.dragText}>Dragging...</Text>
          </View>
        )}
      </View>
    </Draggable>
  );
}

Custom Animation Functions

function AnimatedDraggable({ data }) {
  // Bounce animation
  const bounceAnimation = (toValue) => {
    "worklet";
    return withTiming(toValue, {
      duration: 600,
      easing: Easing.bounce,
    });
  };

  // Spring animation with custom config
  const customSpring = (toValue) => {
    "worklet";
    return withSpring(toValue, {
      damping: 15,
      stiffness: 150,
      mass: 1,
    });
  };

  // Sequence animation
  const sequenceAnimation = (toValue) => {
    "worklet";
    return withSequence(
      withTiming(toValue * 1.1, { duration: 100 }),
      withSpring(toValue, { damping: 12 })
    );
  };

  return (
    <Draggable
      data={data}
      animationFunction={bounceAnimation} // Try different animations
    >
      <View style={styles.animatedItem}>
        <Text>Custom Animation</Text>
      </View>
    </Draggable>
  );
}

Draggable States

The component tracks three distinct states:

enum DraggableState {
  IDLE = "IDLE", // At rest position
  DRAGGING = "DRAGGING", // Being actively dragged
  DROPPED = "DROPPED", // Successfully dropped
}

State-based Styling

function StatefulDraggable({ data }) {
  const [state, setState] = useState(DraggableState.IDLE);

  const getStateStyle = () => {
    switch (state) {
      case DraggableState.IDLE:
        return styles.idle;
      case DraggableState.DRAGGING:
        return styles.dragging;
      case DraggableState.DROPPED:
        return styles.dropped;
      default:
        return {};
    }
  };

  return (
    <Draggable
      data={data}
      onStateChange={setState}
      style={[styles.base, getStateStyle()]}
    >
      <View style={styles.content}>
        <Text>State: {state}</Text>
      </View>
    </Draggable>
  );
}

Drag Handle Component

The Draggable.Handle component creates specific draggable areas:

Handle Props

PropTypeDescription
childrenReactNodeContent to render inside the handle
styleStyleProp<ViewStyle>Style for the handle container

Handle Examples

// Icon handle
<Draggable.Handle style={styles.iconHandle}>
  <Icon name="drag-handle" size={24} color="#666" />
</Draggable.Handle>

// Custom dots handle
<Draggable.Handle style={styles.dotsHandle}>
  <View style={styles.dotsContainer}>
    <View style={styles.dot} />
    <View style={styles.dot} />
    <View style={styles.dot} />
  </View>
</Draggable.Handle>

// Text handle
<Draggable.Handle style={styles.textHandle}>
  <Text style={styles.handleText}>≡</Text>
</Draggable.Handle>

TypeScript Support

The Draggable component is fully typed with generic support:

interface TaskData {
  id: string;
  title: string;
  priority: "low" | "medium" | "high";
  assignee?: string;
}

// Fully typed draggable
<Draggable<TaskData>
  data={taskData}
  onDragStart={(data: TaskData) => {
    // data is fully typed with TaskData properties
    console.log(data.title, data.priority);
  }}
>
  <TaskComponent />
</Draggable>;

Performance Tips

  • Use React.memo for draggable content that doesn't change frequently
  • Avoid heavy computations in drag callbacks
  • Use useCallback for stable callback references
  • Consider virtualization for large lists of draggables

Accessibility

The Draggable component supports accessibility features:

<Draggable data={data} style={styles.accessibleDraggable}>
  <View
    accessible={true}
    accessibilityRole="button"
    accessibilityLabel={`Draggable item: ${data.title}`}
    accessibilityHint="Double tap and hold to drag this item"
  >
    <Text>{data.title}</Text>
  </View>
</Draggable>

See Also