Overview
Nested drag contexts enable sophisticated drag and drop interactions like sortable lists with multiple levels, tree structures, and kanban boards where both columns and cards are draggable. This guide covers advanced patterns for building hierarchical drag and drop interfaces.What You’ll Build
A nested drag and drop interface featuring:- Multiple sortable containers that can themselves be reordered
- Items that can be dragged between containers
- Different drag types (containers vs. items)
- Proper collision detection for nested elements
- Real-time visual feedback at multiple levels
Prerequisites
You should be comfortable with:- Simple Drag and Drop basics
- Multiple Containers patterns
- React hooks and state management
Core Concepts
Drag Types and Groups
When working with nested contexts, you need to distinguish between different types of draggable elements:typeidentifies what kind of element this isacceptdefines what types can be dropped onto this elementgrouplinks items to their parent container- Different types can have different collision priorities
Collision Priority
Nested elements need different collision priorities to ensure proper drop targets:Implementation
import { useState } from 'react';
function App() {
// State: container ID -> array of item IDs
const [items, setItems] = useState({
A: ['A1', 'A2', 'A3'],
B: ['B1', 'B2', 'B3'],
C: ['C1', 'C2', 'C3'],
D: []
});
// Column order
const [columns] = useState(['A', 'B', 'C', 'D']);
}
import { useSortable } from '@dnd-kit/react/sortable';
import { Feedback } from '@dnd-kit/dom';
function SortableItem({
id,
column,
index
}: {
id: string;
column: string;
index: number;
}) {
const { handleRef, ref, isDragging } = useSortable({
id,
group: column, // Items belong to a column group
accept: 'item', // Only accept other items
type: 'item', // This is an item type
index,
data: { group: column }, // Store group info
plugins: [
Feedback.configure({ feedback: 'clone' }) // Show clone during drag
]
});
return (
<div
ref={ref}
className="item"
style={{ opacity: isDragging ? 0.5 : 1 }}
>
{id}
<button ref={handleRef} className="handle" aria-label="Drag handle">
⋮⋮
</button>
</div>
);
}
group links the item to its parent containerhandleRef provides a drag handle (optional)Feedback.configure creates a visual clone during draggingdata for use in drag handlersimport { CollisionPriority } from '@dnd-kit/abstract';
function SortableColumn({
id,
index,
items
}: {
id: string;
index: number;
items: string[];
}) {
const { handleRef, ref, isDragging } = useSortable({
id,
accept: ['column', 'item'], // Can accept both columns and items
collisionPriority: CollisionPriority.Low, // Lower priority than items
type: 'column',
index
});
return (
<div
ref={ref}
className="column"
style={{ opacity: isDragging ? 0.5 : 1 }}
>
<h2>
{id}
<button ref={handleRef} className="handle" aria-label="Drag column">
⋮⋮
</button>
</h2>
<ul className="items-list">
{items.map((itemId, itemIndex) => (
<SortableItem
key={itemId}
id={itemId}
column={id}
index={itemIndex}
/>
))}
</ul>
</div>
);
}
import { DragDropProvider } from '@dnd-kit/react';
import { move } from '@dnd-kit/helpers';
import { useCallback, useRef } from 'react';
function App() {
const [items, setItems] = useState({
A: ['A1', 'A2', 'A3'],
B: ['B1', 'B2', 'B3'],
C: ['C1', 'C2', 'C3'],
D: []
});
const [columns] = useState(Object.keys(items));
const snapshot = useRef(structuredClone(items));
return (
<DragDropProvider
onDragStart={useCallback(() => {
// Save snapshot for potential cancellation
snapshot.current = structuredClone(items);
}, [items])}
onDragOver={useCallback((event) => {
const { source } = event.operation;
// Don't move columns during drag over
if (source && source.type === 'column') {
return;
}
// Move items in real-time
setItems((items) => move(items, event));
}, [])}
onDragEnd={useCallback((event) => {
if (event.canceled) {
// Restore from snapshot
setItems(snapshot.current);
return;
}
const { source } = event.operation;
// Handle column reordering if needed
if (source?.type === 'column') {
// Implement column reordering logic
}
}, [])}
>
<div className="board">
{columns.map((column, columnIndex) => (
<SortableColumn
key={column}
id={column}
index={columnIndex}
items={items[column]}
/>
))}
</div>
</DragDropProvider>
);
}
onDragStart captures a snapshot for cancellationonDragOver provides real-time updates (only for items)onDragEnd finalizes the operation or restores from snapshotsource.type to handle different draggable types differentlymove helper for automatic state updatesfunction SortableItem({ id, column, index }: Props) {
const handleRef = useRef<HTMLButtonElement>(null);
const [element, setElement] = useState<Element | null>(null);
const { isDragging } = useSortable({
id,
index,
element, // The draggable element
handle: handleRef, // Only drag via this handle
group: column,
type: 'item'
});
return (
<div ref={setElement} className="item">
{id}
<button
ref={handleRef}
className="handle"
aria-label="Drag handle"
>
⋮⋮
</button>
</div>
);
}
Complete Example
Here’s a complete multi-level sortable implementation:Styling
Advanced Patterns
Tree Structures
For tree structures with indefinite nesting, track depth and parent relationships:Custom Sensors
Configure sensors for better control:Key Takeaways
- Use
typeandacceptto differentiate between draggable element types - Set
collisionPriorityto control which elements are prioritized as drop targets - The
groupproperty links items to their parent containers - Use
onDragOverfor real-time updates andonDragEndfor finalization - The
movehelper from@dnd-kit/helpershandles complex nested moves - Drag handles provide better UX in complex layouts
- Store snapshots for reliable cancellation behavior
- Separate column reordering logic from item moving logic
Performance Tips
- Use
React.memofor sortable components to prevent unnecessary re-renders - Use
useCallbackfor event handlers to maintain referential equality - Consider virtualization for large lists
- Avoid inline functions in render for better performance
Next Steps
- Explore drag overlays for smoother visual feedback
- Learn about custom collision detection algorithms
- Implement keyboard navigation for accessibility
- Add animations and transitions for polish