Overview
The Sortable class combines Draggable and Droppable functionality to create sortable lists, grids, and other reorderable collections. It handles index tracking, smooth transitions, and optimistic updates automatically.
Installation
import { Sortable } from '@dnd-kit/dom/sortable' ;
Constructor
const sortable = new Sortable ( input , manager );
Parameters
Configuration for the sortable item Unique identifier for this sortable item
Current index within the sortable group
The DOM element to make sortable
Group identifier for multi-list sorting. Items in the same group can be sorted together. Items without a group can only sort within their own list.
The element to use as the droppable target. By default, uses the same element. Useful for drop zones that differ from the visual element.
transition
SortableTransition | null
Transition configuration for position changes. transition : {
duration : 250 , // Milliseconds
easing : 'cubic-bezier(0.25, 1, 0.5, 1)' , // CSS easing
idle : false // Animate when not dragging?
}
Set to null to disable transitions. Plugins specific to this sortable item. Default includes SortableKeyboardPlugin and OptimisticSortingPlugin.
Custom data attached to this sortable
Drag handle element. If not provided, the entire element is draggable.
Whether this sortable is disabled
Type identifier for filtering drag-drop interactions
accept
Type | Type[] | (draggable) => boolean
Types this sortable accepts as drop targets. accept : 'item' // Single type
accept : [ 'item' , 'card' ] // Multiple types
accept : ( draggable ) => { ... } // Function
Override sensors for this sortable
Position modifiers for this sortable
Custom collision detection algorithm
Priority for collision resolution. Higher values win.
The DragDropManager instance
Properties
index
sortable . index = 2 ;
console . log ( sortable . index ); // 2
Current index within the sortable group. Updating this triggers a transition animation.
initialIndex
console . log ( sortable . initialIndex ); // Index when drag started
The index when the current drag operation started. Read-only.
group
sortable . group = 'list-1' ;
console . log ( sortable . group ); // 'list-1'
Group identifier. Items in the same group can be sorted together.
initialGroup
console . log ( sortable . initialGroup ); // Group when drag started
The group when the current drag operation started. Read-only.
element
sortable . element = document . getElementById ( 'item-1' );
The sortable’s DOM element.
target
sortable . target = document . getElementById ( 'drop-zone-1' );
The droppable target element. Defaults to element.
isDragSource
if ( sortable . isDragSource ) {
console . log ( 'This item is being dragged' );
}
Whether this sortable is the source of the current drag. Read-only.
isDragging
if ( sortable . isDragging ) {
console . log ( 'This item is being dragged' );
}
Whether this sortable is currently being dragged. Read-only.
isDropTarget
if ( sortable . isDropTarget ) {
console . log ( 'Something is being dragged over this item' );
}
Whether something is being dragged over this sortable. Read-only.
disabled
sortable . disabled = true ;
Disable both dragging and dropping for this sortable.
Methods
register()
const unregister = sortable . register ();
// Later...
unregister ();
Register the sortable with the manager. Returns a function to unregister.
unregister()
Unregister the sortable from the manager.
destroy()
Destroy the sortable and clean up resources.
refreshShape()
Refresh the sortable’s bounding rectangle. Useful after layout changes.
Default Transition
const defaultSortableTransition : SortableTransition = {
duration: 250 , // Milliseconds
easing: 'cubic-bezier(0.25, 1, 0.5, 1)' , // Smooth easing
idle: false // Don't animate when not dragging
};
Basic Example
import { DragDropManager } from '@dnd-kit/dom' ;
import { Sortable } from '@dnd-kit/dom/sortable' ;
const manager = new DragDropManager ();
const items = [ 'Item 1' , 'Item 2' , 'Item 3' ];
const sortables : Sortable [] = [];
items . forEach (( item , index ) => {
const element = document . getElementById ( `item- ${ index } ` );
const sortable = new Sortable (
{
id: `item- ${ index } ` ,
index ,
element
},
manager
);
sortable . register ();
sortables . push ( sortable );
});
// Update indices on dragend
manager . monitor . addEventListener ( 'dragend' , ( event ) => {
if ( ! event . target ) return ;
// Reorder logic here
const oldIndex = event . source . index ;
const newIndex = event . target . index ;
// Update your data model
const [ removed ] = items . splice ( oldIndex , 1 );
items . splice ( newIndex , 0 , removed );
// Update sortable indices
sortables . forEach (( sortable , i ) => {
sortable . index = i ;
});
});
Multi-List Example
import { DragDropManager } from '@dnd-kit/dom' ;
import { Sortable } from '@dnd-kit/dom/sortable' ;
const manager = new DragDropManager ();
const lists = {
'list-1' : [ 'A' , 'B' , 'C' ],
'list-2' : [ 'D' , 'E' , 'F' ]
};
const sortables = new Map < string , Sortable >();
Object . entries ( lists ). forEach (([ listId , items ]) => {
items . forEach (( item , index ) => {
const element = document . getElementById ( ` ${ listId } -item- ${ index } ` );
const sortable = new Sortable (
{
id: ` ${ listId } - ${ index } ` ,
index ,
group: listId , // Items in same group can sort together
element ,
accept: 'sortable-item' , // Accept any sortable item
type: 'sortable-item'
},
manager
);
sortable . register ();
sortables . set ( sortable . id , sortable );
});
});
manager . monitor . addEventListener ( 'dragend' , ( event ) => {
if ( ! event . target ) return ;
const source = event . source as Sortable ;
const target = event . target as Sortable ;
const sourceList = source . initialGroup ;
const targetList = target . group ;
// Same list reorder
if ( sourceList === targetList ) {
const items = lists [ sourceList ];
const [ removed ] = items . splice ( source . initialIndex , 1 );
items . splice ( target . index , 0 , removed );
}
// Cross-list move
else {
const sourceItems = lists [ sourceList ];
const targetItems = lists [ targetList ];
const [ removed ] = sourceItems . splice ( source . initialIndex , 1 );
targetItems . splice ( target . index , 0 , removed );
}
// Update all indices
Object . entries ( lists ). forEach (([ listId , items ]) => {
items . forEach (( item , index ) => {
const sortable = sortables . get ( ` ${ listId } - ${ index } ` );
if ( sortable ) {
sortable . index = index ;
sortable . group = listId ;
}
});
});
});
Grid Example
import { DragDropManager } from '@dnd-kit/dom' ;
import { Sortable } from '@dnd-kit/dom/sortable' ;
const manager = new DragDropManager ();
const GRID_COLS = 4 ;
const items = Array . from ({ length: 12 }, ( _ , i ) => `Item ${ i + 1 } ` );
const sortables = items . map (( item , index ) => {
const element = document . getElementById ( `grid-item- ${ index } ` );
const sortable = new Sortable (
{
id: `grid- ${ index } ` ,
index ,
element ,
transition: {
duration: 200 ,
easing: 'ease-out'
}
},
manager
);
sortable . register ();
return sortable ;
});
manager . monitor . addEventListener ( 'dragend' , ( event ) => {
if ( ! event . target ) return ;
const oldIndex = event . source . index ;
const newIndex = event . target . index ;
// Reorder items
const [ removed ] = items . splice ( oldIndex , 1 );
items . splice ( newIndex , 0 , removed );
// Update all indices
sortables . forEach (( sortable , i ) => {
sortable . index = i ;
});
});
Custom Transitions
const sortable = new Sortable (
{
id: 'item-1' ,
index: 0 ,
element: document . getElementById ( 'item-1' ),
transition: {
duration: 300 ,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)' ,
idle: true // Animate even when not dragging
}
},
manager
);
// Or disable transitions
const sortableNoTransition = new Sortable (
{
id: 'item-2' ,
index: 1 ,
element: document . getElementById ( 'item-2' ),
transition: null // No animation
},
manager
);
Built-in Plugins
Sortable includes two plugins by default:
SortableKeyboardPlugin
Enhances keyboard navigation for sortable lists:
Up/Down arrow keys move between items
Automatically reorders on arrow key press
Works with screen readers
OptimisticSortingPlugin
Provides optimistic updates during dragging:
Items animate to their new positions before drop
Smooth reordering as you drag over items
Automatically reverts if drag is canceled
Type Definitions
interface SortableInput < T extends Data >
extends DraggableInput < T >, DroppableInput < T > {
index : number ;
target ?: Element ;
group ?: UniqueIdentifier ;
transition ?: SortableTransition | null ;
plugins ?: Plugins ;
}
interface SortableTransition {
duration ?: number ;
easing ?: string ;
idle ?: boolean ;
}
class Sortable < T extends Data = Data > {
constructor ( input : SortableInput < T >, manager : DragDropManager );
index : number ;
readonly initialIndex : number ;
group : UniqueIdentifier | undefined ;
readonly initialGroup : UniqueIdentifier | undefined ;
element : Element | undefined ;
target : Element | undefined ;
readonly isDragSource : boolean ;
readonly isDragging : boolean ;
readonly isDropTarget : boolean ;
disabled : boolean ;
register () : () => void ;
unregister () : void ;
destroy () : void ;
refreshShape () : Rectangle | undefined ;
}
Additional Exports
SortableDraggable
A specialized Draggable class optimized for sortable items:
import { SortableDraggable } from '@dnd-kit/dom/sortable' ;
class SortableDraggable < T extends Data = Data > extends Draggable < T > {
// Specialized for sortable use cases
}
SortableDroppable
A specialized Droppable class optimized for sortable drop targets:
import { SortableDroppable } from '@dnd-kit/dom/sortable' ;
class SortableDroppable < T extends Data = Data > extends Droppable < T > {
// Specialized for sortable use cases
}
defaultSortableTransition
The default transition configuration for sortable items:
import { defaultSortableTransition } from '@dnd-kit/dom/sortable' ;
const defaultTransition : SortableTransition = {
duration: 250 ,
easing: 'ease' ,
idle: false
};
Utility Functions
isSortable
Type guard to check if an entity is a sortable:
import { isSortable } from '@dnd-kit/dom/sortable' ;
if ( isSortable ( entity )) {
console . log ( 'Index:' , entity . index );
console . log ( 'Group:' , entity . group );
}
isSortableOperation
Type guard to check if a drag operation involves sortables:
import { isSortableOperation } from '@dnd-kit/dom/sortable' ;
manager . monitor . addEventListener ( 'dragover' , ( event ) => {
if ( isSortableOperation ( event . operation )) {
console . log ( 'Source index:' , event . operation . source . index );
console . log ( 'Target index:' , event . operation . target ?. index );
}
});