Overview
Once you’ve mastered the basics, you’ll often need to handle multiple drop containers. This guide shows you how to create a drag and drop interface where items can be moved between multiple containers, like a kanban board or card organizer.What You’ll Build
A multi-container drag and drop interface featuring:- Multiple drop zones with unique identities
- Ability to drag items between any container
- Visual feedback showing which container will receive the item
- State management for tracking items across containers
Prerequisites
This guide assumes you understand the basics from the Simple Drag and Drop guide.Implementation
Start by defining your containers and their initial state. Use an object where keys are container IDs and values are arrays of item IDs.
import { useState } from 'react';
function App() {
const [items, setItems] = useState({
A: ['A1', 'A2', 'A3'],
B: ['B1', 'B2', 'B3'],
C: ['C1', 'C2', 'C3'],
D: []
});
// Containers can be derived from the items object
const containers = Object.keys(items); // ['A', 'B', 'C', 'D']
}
Create draggable items that know which container they belong to. This is important for proper state management.
import { useDraggable } from '@dnd-kit/react';
function DraggableItem({ id, container }: { id: string; container: string }) {
const { ref, isDragging } = useDraggable({
id,
data: { container } // Store container info for later use
});
return (
<div
ref={ref}
className="item"
style={{ opacity: isDragging ? 0.5 : 1 }}
>
{id}
</div>
);
}
data objectisDragging to provide visual feedbackimport { useDroppable } from '@dnd-kit/react';
function Container({
id,
items
}: {
id: string;
items: string[];
}) {
const { ref, isDropTarget } = useDroppable({ id });
return (
<div
ref={ref}
className={`container ${isDropTarget ? 'active' : ''}`}
>
<h3>Container {id}</h3>
<div className="items">
{items.map((itemId) => (
<DraggableItem
key={itemId}
id={itemId}
container={id}
/>
))}
</div>
</div>
);
}
import { DragDropProvider } from '@dnd-kit/react';
function App() {
const [items, setItems] = useState({
A: ['A1', 'A2', 'A3'],
B: ['B1', 'B2', 'B3'],
C: ['C1', 'C2', 'C3'],
D: []
});
return (
<DragDropProvider
onDragEnd={(event) => {
if (event.canceled) return;
const { source, target } = event.operation;
if (!source || !target) return;
const sourceContainer = source.data?.container as string;
const targetContainer = target.id as string;
setItems((prev) => {
const newItems = { ...prev };
// Remove from source container
newItems[sourceContainer] = newItems[sourceContainer].filter(
(id) => id !== source.id
);
// Add to target container
newItems[targetContainer] = [
...newItems[targetContainer],
source.id as string
];
return newItems;
});
}}
>
<div className="grid">
{Object.entries(items).map(([containerId, containerItems]) => (
<Container
key={containerId}
id={containerId}
items={containerItems}
/>
))}
</div>
</DragDropProvider>
);
}
Complete Example
Here’s a full working implementation with multiple containers:Styling
Advanced: Using the Move Helper
For sortable lists within containers, dnd-kit provides amove helper from @dnd-kit/helpers:
Key Takeaways
- Structure your state as an object mapping container IDs to item arrays
- Store container information in the draggable’s
dataproperty - Use
event.operation.sourceandevent.operation.targetto identify containers - Remove items from the source container and add to the target container
- The
movehelper from@dnd-kit/helperscan simplify state updates - Visual feedback helps users understand where items can be dropped
Common Patterns
Preventing Drops in Certain Containers
Limiting Items Per Container
Next Steps
- Explore Nested Contexts for hierarchical structures
- Learn about sortable lists for ordering within containers
- Add animations with drag overlays for smoother interactions