Skip to main content

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