Overview
The React adapter provides hooks and components that seamlessly integrate drag and drop into your React applications with full TypeScript support.
Installation
Install the React package and its dependencies:
npm install @dnd-kit/react @dnd-kit/dom @dnd-kit/abstract
Requirements: React 18.0.0 or higher
Getting Started
Add DragDropProvider
Wrap your application with the DragDropProvider:import {DragDropProvider} from '@dnd-kit/react';
function App() {
return (
<DragDropProvider>
{/* Your app content */}
</DragDropProvider>
);
}
Create draggable components
Use the useDraggable hook to make elements draggable:import {useDraggable} from '@dnd-kit/react';
function DraggableCard({id, title}) {
const {ref, isDragging} = useDraggable({
id,
data: {title},
});
return (
<div ref={ref} style={{opacity: isDragging ? 0.5 : 1}}>
{title}
</div>
);
}
Create droppable zones
Use the useDroppable hook to create drop targets:import {useDroppable} from '@dnd-kit/react';
function DroppableZone({id, children}) {
const {ref, isDropTarget} = useDroppable({id});
return (
<div
ref={ref}
style={{
backgroundColor: isDropTarget ? 'lightblue' : 'transparent',
}}
>
{children}
</div>
);
}
Handle drag events
Listen to events via the provider props:function App() {
const handleDragEnd = (event) => {
const {source, target} = event.operation;
console.log('Dropped', source.id, 'on', target?.id);
};
return (
<DragDropProvider onDragEnd={handleDragEnd}>
{/* Your components */}
</DragDropProvider>
);
}
Complete Example
Here’s a full drag and drop implementation:
import {useState} from 'react';
import {DragDropProvider, useDraggable, useDroppable} from '@dnd-kit/react';
function DraggableItem({id, label}) {
const {ref, isDragging, isDragSource} = useDraggable({
id,
data: {label},
});
return (
<div
ref={ref}
style={{
padding: '16px',
margin: '8px',
backgroundColor: isDragging ? '#e3f2fd' : '#f5f5f5',
border: isDragSource ? '2px solid #2196f3' : '1px solid #ddd',
borderRadius: '4px',
cursor: 'grab',
}}
>
{label}
</div>
);
}
function DroppableZone({id, title, items}) {
const {ref, isDropTarget} = useDroppable({id});
return (
<div
ref={ref}
style={{
minHeight: '200px',
padding: '16px',
margin: '8px',
backgroundColor: isDropTarget ? '#e8f5e9' : '#fafafa',
border: '2px dashed #ccc',
borderRadius: '8px',
}}
>
<h3>{title}</h3>
{items.map((item) => (
<DraggableItem key={item.id} id={item.id} label={item.label} />
))}
</div>
);
}
function App() {
const [zones, setZones] = useState({
'zone-1': [
{id: 'item-1', label: 'Item 1'},
{id: 'item-2', label: 'Item 2'},
],
'zone-2': [
{id: 'item-3', label: 'Item 3'},
],
});
const handleDragEnd = (event) => {
const {source, target} = event.operation;
if (!target) return;
setZones((prev) => {
const newZones = {...prev};
// Remove from source zone
Object.keys(newZones).forEach((zoneId) => {
newZones[zoneId] = newZones[zoneId].filter(
(item) => item.id !== source.id
);
});
// Add to target zone
const item = source.data;
newZones[target.id] = [...newZones[target.id], item];
return newZones;
});
};
return (
<DragDropProvider onDragEnd={handleDragEnd}>
<div style={{display: 'flex', gap: '16px', padding: '24px'}}>
<DroppableZone id="zone-1" title="Zone 1" items={zones['zone-1']} />
<DroppableZone id="zone-2" title="Zone 2" items={zones['zone-2']} />
</div>
</DragDropProvider>
);
}
Sortable Lists
Create sortable lists with the useSortable hook:
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
import {useState} from 'react';
function SortableItem({id, index, label}) {
const {ref, isDragging, isDragSource} = useSortable({
id,
index,
group: 'list',
data: {label},
});
return (
<div
ref={ref}
style={{
padding: '12px',
margin: '4px 0',
backgroundColor: isDragging ? '#e3f2fd' : 'white',
border: isDragSource ? '2px solid #2196f3' : '1px solid #ddd',
borderRadius: '4px',
cursor: 'grab',
}}
>
{label}
</div>
);
}
function SortableList() {
const [items, setItems] = useState([
{id: '1', label: 'Item 1'},
{id: '2', label: 'Item 2'},
{id: '3', label: 'Item 3'},
{id: '4', label: 'Item 4'},
]);
const handleDragEnd = (event) => {
const {source, target} = event.operation;
if (!target || source.index === target.index) return;
setItems((prev) => {
const newItems = [...prev];
const [removed] = newItems.splice(source.index, 1);
newItems.splice(target.index, 0, removed);
return newItems;
});
};
return (
<DragDropProvider onDragEnd={handleDragEnd}>
<div style={{maxWidth: '400px', padding: '24px'}}>
{items.map((item, index) => (
<SortableItem
key={item.id}
id={item.id}
index={index}
label={item.label}
/>
))}
</div>
</DragDropProvider>
);
}
Hook APIs
useDraggable
The useDraggable hook signature from /home/daytona/workspace/source/packages/react/src/core/draggable/useDraggable.ts:21:
function useDraggable<T extends Data = Data>(
input: UseDraggableInput<T>
): {
draggable: Draggable<T>;
isDragging: boolean;
isDropping: boolean;
isDragSource: boolean;
ref: (element: Element | null) => void;
handleRef: (element: Element | null) => void;
}
interface UseDraggableInput<T extends Data = Data> {
id: string;
element?: RefOrValue<Element>;
handle?: RefOrValue<Element>;
data?: T;
disabled?: boolean;
modifiers?: Modifier[];
sensors?: Sensor[];
plugins?: Plugin[];
alignment?: Alignment;
}
useDroppable
The useDroppable hook signature from /home/daytona/workspace/source/packages/react/src/core/droppable/useDroppable.ts:22:
function useDroppable<T extends Data = Data>(
input: UseDroppableInput<T>
): {
droppable: Droppable<T>;
isDropTarget: boolean;
ref: (element: Element | null) => void;
}
interface UseDroppableInput<T extends Data = Data> {
id: string;
element?: RefOrValue<Element>;
data?: T;
disabled?: boolean;
accept?: string | string[];
type?: string;
collisionDetector?: CollisionDetector;
}
useSortable
The useSortable hook signature from /home/daytona/workspace/source/packages/react/src/sortable/useSortable.ts:23:
function useSortable<T extends Data = Data>(
input: UseSortableInput<T>
): {
sortable: Sortable<T>;
isDragging: boolean;
isDropping: boolean;
isDragSource: boolean;
isDropTarget: boolean;
ref: (element: Element | null) => void;
handleRef: (element: Element | null) => void;
sourceRef: (element: Element | null) => void;
targetRef: (element: Element | null) => void;
}
interface UseSortableInput<T extends Data = Data> {
id: string;
group: string;
index: number;
element?: RefOrValue<Element>;
handle?: RefOrValue<Element>;
target?: RefOrValue<Element>;
data?: T;
disabled?: boolean;
type?: string;
accept?: string | string[];
modifiers?: Modifier[];
sensors?: Sensor[];
plugins?: Plugin[];
collisionDetector?: CollisionDetector;
collisionPriority?: number;
transition?: SortableTransition;
alignment?: Alignment;
}
Advanced Features
Drag Handles
Use separate elements for dragging:
function DraggableCard({id, title}) {
const {ref, handleRef, isDragging} = useDraggable({id});
return (
<div ref={ref} style={{opacity: isDragging ? 0.5 : 1}}>
<div ref={handleRef} style={{cursor: 'grab', padding: '8px'}}>
☰
</div>
<div>{title}</div>
</div>
);
}
Modifiers
Constrain drag behavior:
import {restrictToVerticalAxis} from '@dnd-kit/abstract/modifiers';
function App() {
return (
<DragDropProvider modifiers={[restrictToVerticalAxis]}>
{/* Content */}
</DragDropProvider>
);
}
Sensors
Customize activation:
import {PointerSensor} from '@dnd-kit/dom';
function App() {
const sensors = [
new PointerSensor({
activationConstraint: {
delay: 250,
tolerance: 5,
},
}),
];
return (
<DragDropProvider sensors={sensors}>
{/* Content */}
</DragDropProvider>
);
}
Type Safety
Define custom data types:
interface TaskData {
id: string;
title: string;
priority: 'low' | 'medium' | 'high';
}
function TaskCard({task}: {task: TaskData}) {
const {ref, isDragging} = useDraggable<TaskData>({
id: task.id,
data: task,
});
return (
<div ref={ref}>
<h3>{task.title}</h3>
<span>Priority: {task.priority}</span>
</div>
);
}
Best Practices
- Stable IDs: Use consistent, unique IDs for draggables and droppables
- Memoization: Memoize expensive computations in event handlers
- Ref forwarding: When wrapping components, forward refs properly
- State updates: Batch state updates in drag event handlers
- Performance: Use React’s memo/useMemo for large lists
Next Steps
Sortable Lists
Build sortable lists with animations
Multiple Containers
Drag between multiple containers
Sensors
Configure interaction methods
Events
Handle drag and drop events