The useSortable hook combines draggable and droppable functionality to create sortable list items. It’s specifically designed for building sortable lists, grids, and other reorderable collections.
Usage
import {useSortable} from '@dnd-kit/react/sortable';
function SortableItem({id, index, group}) {
const {ref, isDragging, isDropTarget} = useSortable({
id,
index,
group,
});
return (
<div
ref={ref}
style={{
opacity: isDragging ? 0.5 : 1,
outline: isDropTarget ? '2px solid blue' : 'none',
}}
>
Item {id}
</div>
);
}
Parameters
Unique identifier for this sortable item.const {ref} = useSortable({id: 'item-1', index: 0, group: 'list'});
Current position of this item in the sortable list. This is used to calculate transitions and reordering.const {ref} = useSortable({
id: item.id,
index: 0, // First item in the list
group: 'list',
});
Group identifier that links sortable items together. Items with the same group can be reordered among each other.// Items in the same group can be sorted together
const {ref} = useSortable({
id: 'item-1',
index: 0,
group: 'my-list',
});
element
Element | RefObject<Element>
The DOM element for this sortable item. If not provided, use the ref from the return value.
handle
Element | RefObject<Element>
A specific element to use as the drag handle. If provided, dragging can only be initiated from this element.function SortableWithHandle() {
const {ref, handleRef} = useSortable({
id: 'item',
index: 0,
group: 'list',
});
return (
<div ref={ref}>
<div ref={handleRef} style={{cursor: 'grab'}}>⋮⋮</div>
<div>Content</div>
</div>
);
}
target
Element | RefObject<Element>
The target element for drop detection. Useful for sortable items with complex layouts where the drop target differs from the visual element.
Custom data to attach to this sortable item.const {ref} = useSortable({
id: 'task-1',
index: 0,
group: 'tasks',
data: {label: 'Complete documentation', priority: 'high'},
});
Whether this sortable item is disabled. Disabled items cannot be dragged or act as drop targets.const {ref} = useSortable({
id: 'item',
index: 0,
group: 'list',
disabled: isLocked,
});
Type identifier for this sortable. Can be used to restrict which items can be sorted together.
Type or types of draggables this sortable accepts as drop targets.const {ref} = useSortable({
id: 'item',
index: 0,
group: 'list',
accept: ['task', 'subtask'],
});
Animation configuration for the sortable transition effects.const {ref} = useSortable({
id: 'item',
index: 0,
group: 'list',
transition: {
duration: 200,
easing: 'ease-in-out',
},
});
Custom collision detection algorithm for this sortable.
Priority for collision detection when multiple sortables overlap. Higher values have higher priority.
Override the sensors for this specific sortable. By default, inherits from DragDropProvider.
Modifiers that transform the position during drag. Applied in addition to provider-level modifiers.
Plugins specific to this sortable. Applied in addition to provider-level plugins.
Alignment point for the drag source, relative to the element’s position. Values are percentages (0-1).
Return Value
ref
(element: Element | null) => void
Callback ref to attach to the sortable element.const {ref} = useSortable({id: 'item', index: 0, group: 'list'});
return <div ref={ref}>Sortable item</div>;
handleRef
(element: Element | null) => void
Callback ref to attach to a drag handle element.const {ref, handleRef} = useSortable({id: 'item', index: 0, group: 'list'});
return (
<div ref={ref}>
<div ref={handleRef}>⋮⋮</div>
</div>
);
targetRef
(element: Element | null) => void
Callback ref to attach to the drop target element (if different from the main element).
sourceRef
(element: Element | null) => void
Callback ref to attach to the drag source element (for advanced use cases).
The underlying Sortable instance. Access this for advanced use cases.
Whether this element is currently being dragged.const {ref, isDragging} = useSortable({id: 'item', index: 0, group: 'list'});
return (
<div ref={ref} style={{opacity: isDragging ? 0.5 : 1}}>
Item
</div>
);
Whether this element is in the dropping animation phase.
Whether this element is the source of the current drag operation.
Whether this element is currently the target of a drag operation.const {ref, isDropTarget} = useSortable({id: 'item', index: 0, group: 'list'});
return (
<div
ref={ref}
style={{outline: isDropTarget ? '2px solid blue' : 'none'}}
>
Item
</div>
);
Examples
Basic Sortable List
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
import {useState} from 'react';
function SortableList() {
const [items, setItems] = useState(['A', 'B', 'C', 'D']);
const handleDragEnd = (event) => {
if (event.canceled) return;
const {source, target} = event.operation;
if (!target) return;
const sourceIndex = source.data.index;
const targetIndex = target.data.index;
setItems((prev) => {
const newItems = [...prev];
const [removed] = newItems.splice(sourceIndex, 1);
newItems.splice(targetIndex, 0, removed);
return newItems;
});
};
return (
<DragDropProvider onDragEnd={handleDragEnd}>
<div>
{items.map((item, index) => (
<SortableItem key={item} id={item} index={index} />
))}
</div>
</DragDropProvider>
);
}
function SortableItem({id, index}) {
const {ref, isDragging} = useSortable({
id,
index,
group: 'list',
data: {index}, // Store index for reordering
});
return (
<div
ref={ref}
style={{
padding: '15px',
margin: '5px 0',
background: isDragging ? '#f0f0f0' : 'white',
border: '1px solid #ddd',
cursor: 'grab',
}}
>
{id}
</div>
);
}
Sortable with Drag Handle
function SortableWithHandle({id, index, label}) {
const {ref, handleRef, isDragging, isDropTarget} = useSortable({
id,
index,
group: 'tasks',
});
return (
<div
ref={ref}
style={{
display: 'flex',
alignItems: 'center',
padding: '10px',
background: isDragging ? '#f0f0f0' : 'white',
border: isDropTarget ? '2px solid blue' : '1px solid #ddd',
marginBottom: '5px',
}}
>
<div
ref={handleRef}
style={{
marginRight: '10px',
cursor: isDragging ? 'grabbing' : 'grab',
fontSize: '20px',
}}
>
⋮⋮
</div>
<div style={{flex: 1}}>{label}</div>
</div>
);
}
Multiple Sortable Lists
function MultipleList() {
const [listA, setListA] = useState(['A1', 'A2', 'A3']);
const [listB, setListB] = useState(['B1', 'B2', 'B3']);
return (
<DragDropProvider onDragEnd={handleDragEnd}>
<div style={{display: 'flex', gap: '20px'}}>
<div>
<h3>List A</h3>
{listA.map((id, index) => (
<SortableItem key={id} id={id} index={index} group="listA" />
))}
</div>
<div>
<h3>List B</h3>
{listB.map((id, index) => (
<SortableItem key={id} id={id} index={index} group="listB" />
))}
</div>
</div>
</DragDropProvider>
);
}
function SortableItem({id, index, group}) {
const {ref, isDragging} = useSortable({
id,
index,
group,
data: {group, index},
});
return <div ref={ref}>{id}</div>;
}
Sortable Grid
function SortableGrid() {
const [items, setItems] = useState([
{id: '1', color: '#ff6b6b'},
{id: '2', color: '#4ecdc4'},
{id: '3', color: '#45b7d1'},
{id: '4', color: '#96ceb4'},
{id: '5', color: '#ffeaa7'},
{id: '6', color: '#dfe6e9'},
]);
return (
<DragDropProvider onDragEnd={handleDragEnd}>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '10px',
}}
>
{items.map((item, index) => (
<SortableGridItem
key={item.id}
id={item.id}
index={index}
color={item.color}
/>
))}
</div>
</DragDropProvider>
);
}
function SortableGridItem({id, index, color}) {
const {ref, isDragging, isDropTarget} = useSortable({
id,
index,
group: 'grid',
});
return (
<div
ref={ref}
style={{
height: '100px',
background: color,
borderRadius: '8px',
opacity: isDragging ? 0.5 : 1,
transform: isDropTarget ? 'scale(1.05)' : 'scale(1)',
transition: 'transform 0.2s',
}}
/>
);
}
Sortable with Custom Transitions
function SmoothSortable({id, index}) {
const {ref, isDragging} = useSortable({
id,
index,
group: 'smooth-list',
transition: {
duration: 300,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
idle: true, // Enable transitions when idle
},
});
return (
<div
ref={ref}
style={{
padding: '20px',
background: 'white',
border: '1px solid #ddd',
opacity: isDragging ? 0.5 : 1,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
}}
>
Item {id}
</div>
);
}
Type Safety
Use TypeScript generics to type the custom data:
interface TaskData {
title: string;
priority: 'low' | 'medium' | 'high';
completed: boolean;
}
function SortableTask({task, index}: {task: TaskData & {id: string}; index: number}) {
const {ref, isDragging} = useSortable<TaskData>({
id: task.id,
index,
group: 'tasks',
data: {
title: task.title,
priority: task.priority,
completed: task.completed,
},
});
return (
<div ref={ref} style={{opacity: isDragging ? 0.5 : 1}}>
<h4>{task.title}</h4>
<span>{task.priority}</span>
</div>
);
}