JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices
—
Extensible plugin architecture for advanced functionality like multi-drag, auto-scroll, and element swapping.
Mount plugins to extend Sortable functionality before creating sortable instances.
/**
* Mount one or more plugins to extend Sortable functionality
* @param plugins - Plugin instances to register
*/
Sortable.mount(...plugins: SortablePlugin[]): void;Usage Examples:
import Sortable, { MultiDrag, AutoScroll, Swap, OnSpill } from "sortablejs";
// Mount individual plugins
Sortable.mount(new MultiDrag());
Sortable.mount(new AutoScroll());
// Mount multiple plugins at once
Sortable.mount(new MultiDrag(), new AutoScroll(), new Swap());
// Now create sortables with plugin functionality
const sortable = Sortable.create(el, {
multiDrag: true, // MultiDrag plugin option
selectedClass: 'selected',
scroll: true, // AutoScroll plugin option
scrollSensitivity: 100
});Enables selection and simultaneous dragging of multiple elements.
/**
* MultiDrag plugin options
*/
interface MultiDragOptions {
/** Enable multi-drag functionality */
multiDrag?: boolean;
/** CSS class for selected elements */
selectedClass?: string;
/** Key to hold for multi-selection (e.g., 'ctrl', 'meta') */
multiDragKey?: string | null;
/** Avoid implicit deselection when clicking outside */
avoidImplicitDeselect?: boolean;
}Usage Examples:
import Sortable, { MultiDrag } from "sortablejs";
Sortable.mount(new MultiDrag());
const sortable = Sortable.create(el, {
multiDrag: true,
selectedClass: 'selected',
multiDragKey: 'ctrl', // Ctrl+click to select multiple
onSelect: (evt) => {
console.log('Item selected:', evt.item);
},
onDeselect: (evt) => {
console.log('Item deselected:', evt.item);
}
});
// Programmatically select items
sortable.select(document.getElementById('item1'));
sortable.select(document.getElementById('item2'));
// Get selected items
const selected = sortable.selectedElements; // HTMLElement[]
// Deselect all
sortable.deselectAll();Automatically scrolls containers when dragging near edges.
/**
* AutoScroll plugin options
*/
interface AutoScrollOptions {
/** Enable auto-scrolling */
scroll?: boolean;
/** Auto-scroll container (defaults to window) */
scrollSensitivity?: number;
/** Auto-scroll speed */
scrollSpeed?: number;
/** Pixels from edge to trigger scrolling */
bubbleScroll?: boolean;
}Usage Examples:
import Sortable, { AutoScroll } from "sortablejs";
Sortable.mount(new AutoScroll());
const sortable = Sortable.create(el, {
scroll: true,
scrollSensitivity: 100, // 100px from edge triggers scroll
scrollSpeed: 20, // Scroll speed
bubbleScroll: true // Allow scrolling in nested containers
});Enables element swapping instead of insertion-based reordering.
/**
* Swap plugin options
*/
interface SwapOptions {
/** Enable swap mode instead of insertion */
swap?: boolean;
/** CSS class applied to swap target */
swapClass?: string;
}Usage Examples:
import Sortable, { Swap } from "sortablejs";
Sortable.mount(new Swap());
const sortable = Sortable.create(el, {
swap: true,
swapClass: 'highlight-swap',
onSwap: (evt) => {
console.log('Elements swapped:', evt.item, evt.swapItem);
}
});Handles elements that are dragged outside valid drop zones.
/**
* OnSpill plugin options
*/
interface OnSpillOptions {
/** Remove element when dragged outside valid drop zone */
removeOnSpill?: boolean;
/** Revert element to original position when spilled */
revertOnSpill?: boolean;
}Usage Examples:
import Sortable, { OnSpill } from "sortablejs";
Sortable.mount(new OnSpill());
const sortable = Sortable.create(el, {
removeOnSpill: true, // Remove element when dragged out
onSpill: (evt) => {
console.log('Element spilled:', evt.item);
// Custom handling for spilled elements
}
});
// Alternative: revert instead of remove
const revertSortable = Sortable.create(el2, {
revertOnSpill: true, // Return to original position when spilled
});Additional events available when MultiDrag plugin is active.
/** Called when an element is selected for multi-drag */
onSelect?: (evt: SortableEvent) => void;
/** Called when an element is deselected */
onDeselect?: (evt: SortableEvent) => void;Additional events available when Swap plugin is active.
/** Called when two elements are swapped */
onSwap?: (evt: SortableEvent & { swapItem: HTMLElement }) => void;Additional events available when OnSpill plugin is active.
/** Called when an element is dragged outside a valid drop zone */
onSpill?: (evt: SortableEvent) => void;Structure for creating custom Sortable plugins.
interface SortablePlugin {
/** Plugin name (must be unique) */
name: string;
/** Default options merged into Sortable defaults */
defaults?: { [key: string]: any };
/** Event options configuration for plugin events */
eventOptions?: { [key: string]: any };
/** Option change listeners for dynamic configuration */
optionListeners?: { [optionName: string]: (value: any) => any };
/** Plugin utilities to extend Sortable.utils */
utils?: { [key: string]: any };
/** Custom event properties injected into SortableEvent */
eventProperties?: (eventName: string) => { [key: string]: any };
/** Initialize plugin on sortable instance creation */
initializePlugin?: (sortable: Sortable) => void;
/** Plugin-specific methods available on sortable instance */
[key: string]: any;
}Custom Plugin Example:
// Comprehensive custom plugin demonstrating all interface features
const AdvancedLoggerPlugin = {
name: 'advancedLogger',
// Default options merged into Sortable defaults
defaults: {
logLevel: 'info',
logToConsole: true,
logToServer: false,
serverEndpoint: '/api/drag-logs'
},
// Custom utilities added to Sortable.utils
utils: {
formatLogMessage: (level, action, item) => {
return `[${new Date().toISOString()}] ${level.toUpperCase()}: ${action} - ${item.textContent}`;
}
},
// Custom event properties injected into all events
eventProperties: function(eventName) {
return {
logTimestamp: Date.now(),
sessionId: this.sessionId
};
},
// Option change listeners for dynamic reconfiguration
optionListeners: {
logLevel: function(newLevel) {
console.log(`Logger level changed to: ${newLevel}`);
return newLevel;
}
},
// Initialize plugin on sortable instance
initializePlugin(sortable) {
// Generate unique session ID
this.sessionId = Math.random().toString(36).substr(2, 9);
// Store reference for event handlers
const plugin = this;
const options = sortable.options;
// Enhanced logging methods on sortable instance
sortable.logDragAction = function(action, item) {
const message = plugin.utils.formatLogMessage(options.logLevel, action, item);
if (options.logToConsole) {
console.log(message);
}
if (options.logToServer) {
fetch(options.serverEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: plugin.sessionId,
action,
timestamp: Date.now(),
item: item.textContent
})
});
}
};
// Hook into events
const originalOnStart = options.onStart;
const originalOnEnd = options.onEnd;
options.onStart = (evt) => {
sortable.logDragAction('DRAG_START', evt.item);
originalOnStart && originalOnStart(evt);
};
options.onEnd = (evt) => {
sortable.logDragAction('DRAG_END', evt.item);
originalOnEnd && originalOnEnd(evt);
};
}
};
// Mount and use the advanced plugin
Sortable.mount(AdvancedLoggerPlugin);
const sortable = Sortable.create(el, {
logLevel: 'debug',
logToServer: true,
serverEndpoint: '/api/custom-logs',
onAdd: (evt) => {
// Custom event properties are automatically available
console.log('Session ID:', evt.sessionId);
console.log('Log timestamp:', evt.logTimestamp);
}
});
// Plugin methods are available on sortable instance
sortable.logDragAction('CUSTOM_ACTION', document.getElementById('item1'));
// Plugin utilities are available globally
console.log(Sortable.utils.formatLogMessage('warn', 'TEST', { textContent: 'Test Item' }));Example showing multiple plugins working together.
import Sortable, { MultiDrag, AutoScroll, Swap, OnSpill } from "sortablejs";
// Mount all plugins
Sortable.mount(new MultiDrag(), new AutoScroll(), new Swap(), new OnSpill());
const sortable = Sortable.create(el, {
// MultiDrag options
multiDrag: true,
selectedClass: 'selected',
multiDragKey: 'ctrl',
// AutoScroll options
scroll: true,
scrollSensitivity: 100,
scrollSpeed: 20,
// Swap options (mutually exclusive with normal sorting)
// swap: true,
// swapClass: 'highlight-swap',
// OnSpill options
revertOnSpill: true,
// Event handlers for plugin functionality
onSelect: (evt) => console.log('Selected:', evt.item),
onDeselect: (evt) => console.log('Deselected:', evt.item),
onSpill: (evt) => console.log('Spilled:', evt.item)
});/**
* MultiDrag - Select and drag multiple elements simultaneously
*/
class MultiDrag implements SortablePlugin {
name: 'multiDrag';
select(element: HTMLElement): void;
deselect(element: HTMLElement): void;
deselectAll(): void;
selectedElements: HTMLElement[];
}
/**
* AutoScroll - Automatic scrolling when dragging near container edges
*/
class AutoScroll implements SortablePlugin {
name: 'autoScroll';
}
/**
* Swap - Element swapping instead of insertion-based reordering
*/
class Swap implements SortablePlugin {
name: 'swap';
}
/**
* OnSpill - Handle elements dragged outside valid drop zones
*/
class OnSpill implements SortablePlugin {
name: 'onSpill';
}Install with Tessl CLI
npx tessl i tessl/npm-sortablejs