# Drag-and-Drop Library Guide
## Table of Contents
- [Library Comparison](#library-comparison)
- [dnd-kit Setup](#dnd-kit-setup)
- [Core Concepts](#core-concepts)
- [Migration Guide](#migration-guide)
- [Alternative Libraries](#alternative-libraries)
## Library Comparison
### Current Landscape (2025)
| Library | Status | Bundle Size | Accessibility | TypeScript | Best For |
|---------|--------|------------|---------------|------------|----------|
| **@dnd-kit** | ✅ Active | ~10KB core | ⭐⭐⭐⭐⭐ | Native | Modern React apps |
| react-beautiful-dnd | ⚠️ Archived | ~30KB | ⭐⭐⭐⭐ | Yes | Legacy projects |
| react-dnd | ✅ Active | ~20KB | ⭐⭐⭐ | Yes | Complex interactions |
| Pragmatic drag-drop | ✅ Active | ~5KB | ⭐⭐⭐⭐ | Native | Atlassian products |
| SortableJS | ✅ Active | ~27KB | ⭐⭐ | No | Vanilla JS |
**Note:** react-beautiful-dnd was archived in August 2025. Migrate to dnd-kit or Pragmatic drag-drop.
## dnd-kit Setup
### Installation
```bash
# Core packages
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
# Optional packages
npm install @dnd-kit/modifiers # Advanced drag modifiers
npm install @dnd-kit/accessibility # Enhanced accessibility
```
### Basic Setup
```tsx
// 1. Import required components
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy
} from '@dnd-kit/sortable';
// 2. Configure sensors
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // Prevent accidental activation
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
// 3. Set up context
function App() {
return (
{/* Your draggable content */}
);
}
```
### TypeScript Configuration
```typescript
// types/dnd.ts
import type { Active, Over } from '@dnd-kit/core';
export interface DragEndEvent {
active: Active;
over: Over | null;
}
export interface DraggableItem {
id: string;
content: React.ReactNode;
order?: number;
}
export interface DragState {
activeId: string | null;
overId: string | null;
isDragging: boolean;
}
```
## Core Concepts
### Sensors
Sensors detect different input methods for initiating drag operations.
```tsx
// Pointer Sensor - Mouse and touch
useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // Movement threshold
delay: 250, // Long press for touch
tolerance: 5, // Movement tolerance during delay
},
});
// Keyboard Sensor - Accessibility
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
scrollBehavior: 'smooth',
});
// Custom Touch Sensor
class TouchSensor extends PointerSensor {
static activators = [
{
eventName: 'onTouchStart',
handler: ({ nativeEvent: event }) => {
return event.touches.length === 1;
},
},
];
}
```
### Collision Detection
Algorithms to determine drop targets.
```tsx
import {
closestCenter, // Closest to center point
closestCorners, // Closest to any corner
rectIntersection, // Rectangle intersection
pointerWithin, // Pointer within bounds
} from '@dnd-kit/core';
// Custom collision detection
function customCollisionDetection(args) {
// First try pointer within
const pointerCollisions = pointerWithin(args);
if (pointerCollisions.length > 0) {
return pointerCollisions;
}
// Fallback to closest center
return closestCenter(args);
}
```
### Modifiers
Transform drag behavior.
```tsx
import { restrictToVerticalAxis, restrictToWindowEdges } from '@dnd-kit/modifiers';
{/* Constrained dragging */}
```
### Drag Overlay
Custom drag preview.
```tsx
import { DragOverlay } from '@dnd-kit/core';
function App() {
const [activeId, setActiveId] = useState(null);
return (
setActiveId(active.id)}>
{/* Regular content */}
{activeId ? : null}
);
}
```
## Migration Guide
### From react-beautiful-dnd to dnd-kit
#### Key Differences
| Feature | react-beautiful-dnd | dnd-kit |
|---------|-------------------|---------|
| API Style | Higher-level | Lower-level, composable |
| Drag Preview | Built-in | Custom via DragOverlay |
| Animation | Automatic | Manual control |
| Accessibility | Good | Excellent |
| Touch Support | Basic | Advanced |
#### Migration Steps
**1. Replace Imports**
```tsx
// Before (react-beautiful-dnd)
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
// After (dnd-kit)
import { DndContext, SortableContext, useSortable } from '@dnd-kit/sortable';
```
**2. Update Context Structure**
```tsx
// Before
{(provided) => (
{items.map((item, index) => (
{(provided, snapshot) => (
{item.content}
)}
))}
{provided.placeholder}
)}
// After
{items.map(item => (
{item.content}
))}
```
**3. Create Sortable Item Component**
```tsx
function SortableItem({ id, children }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
return (
{children}
);
}
```
**4. Update Event Handlers**
```tsx
// Before
function onDragEnd(result) {
if (!result.destination) return;
const items = Array.from(this.state.items);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
this.setState({ items });
}
// After
function handleDragEnd(event) {
const { active, over } = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.findIndex(item => item.id === active.id);
const newIndex = items.findIndex(item => item.id === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
}
```
## Alternative Libraries
### react-dnd
**When to Use:**
- Complex drag sources and drop targets
- Need HTML5 backend flexibility
- Custom drag layers
**Setup:**
```tsx
import { useDrag, useDrop, DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
function App() {
return (
{/* Your app */}
);
}
```
### Pragmatic drag-drop (Atlassian)
**When to Use:**
- Minimal bundle size priority
- Atlassian ecosystem
- Simple drag operations
**Setup:**
```tsx
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
// Make element draggable
useEffect(() => {
const element = ref.current;
if (!element) return;
return draggable({
element,
getInitialData: () => ({ id: item.id }),
});
}, [item.id]);
```
### SortableJS
**When to Use:**
- Vanilla JavaScript projects
- Framework agnostic
- Legacy browser support
**Note:** Limited accessibility support and no TypeScript.
## Best Practices
### Performance Optimization
```tsx
// 1. Memoize expensive calculations
const sortedItems = useMemo(
() => items.sort((a, b) => a.order - b.order),
[items]
);
// 2. Use CSS transforms
const style = {
transform: `translate3d(${x}px, ${y}px, 0)`,
willChange: 'transform',
};
// 3. Throttle drag events
const throttledDragMove = useThrottle(handleDragMove, 16); // 60fps
```
### Accessibility First
```tsx
// Always provide keyboard support
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
// Add ARIA attributes
```
### Touch Device Support
```tsx
// Configure touch activation
useSensor(PointerSensor, {
activationConstraint: {
delay: 250, // Long press
tolerance: 5, // Movement tolerance
},
});
// Prevent scrolling during drag
useEffect(() => {
if (isDragging) {
document.body.style.overflow = 'hidden';
document.body.style.touchAction = 'none';
}
return () => {
document.body.style.overflow = '';
document.body.style.touchAction = '';
};
}, [isDragging]);
```
## Troubleshooting
### Common Issues
**Issue: Drag not initiating on touch devices**
- Solution: Add delay to activation constraint
- Check for conflicting touch handlers
**Issue: Performance lag with many items**
- Solution: Implement virtualization
- Use CSS transforms instead of position
- Memoize components
**Issue: Accessibility not working**
- Solution: Ensure keyboard sensor configured
- Add proper ARIA attributes
- Test with screen readers
**Issue: Scroll not working during drag**
- Solution: Configure auto-scroll
- Check overflow styles
- Implement edge detection