Modern TypeScript rewrite of Sortable.js — reorderable drag-and-drop lists.
Alpha — Not ready for production use. This library is under active development and has not been published to npm. APIs will change. If you need a drag-and-drop library today, use SortableJS.
Migrating from Sortable.js? See the migration guide for the option/plugin/breaking-change delta.
group.pull: 'clone' to copy items instead of movingnpm install resortable
Resortable is not yet published to npm (
2.0.0-alpha.1is the current in-repo version). Until first publish, install from a Git ref:npm install jjeff/resortable.
Once published, the UMD bundle exposes a window.Sortable global and works without a bundler. Version-pin to avoid surprise breaking changes:
<!-- unpkg -->
<script src="https://unpkg.com/resortable@2.0.0-alpha.1/dist/sortable.umd.js"></script>
<!-- or jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/resortable@2.0.0-alpha.1/dist/sortable.umd.js"></script>
<script>
// The UMD build attaches the library as `window.Sortable`
new Sortable(document.getElementById('my-list'), {
animation: 150,
});
</script>
The ESM build via npm install resortable is the recommended path for app code — it tree-shakes, ships TypeScript types, and integrates with modern bundlers.
import { Sortable } from 'resortable';
const sortable = new Sortable(document.getElementById('my-list'), {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: (evt) => {
console.log(`Moved from ${evt.oldIndex} to ${evt.newIndex}`);
}
});
new Sortable(element, {
// Behavior
group: 'shared', // Group name or { name, pull, put } config
sort: true, // Allow sorting within list
disabled: false, // Disable the sortable
draggable: '.sortable-item', // CSS selector for draggable items
handle: '.drag-handle', // Restrict drag to handle elements
filter: 'input, button', // Prevent drag on these elements
ignore: 'a, img', // Descendants that should NOT initiate drag (default 'a, img')
delay: 0, // Delay in ms before drag starts
delayOnTouchOnly: 0, // Touch-specific delay
touchStartThreshold: 5, // Pixels of movement before cancelling delay
// Visual
animation: 150, // Animation duration in ms
easing: 'cubic-bezier(0.4,0,0.2,1)', // CSS easing
ghostClass: 'sortable-ghost', // Class on the ghost placeholder
chosenClass: 'sortable-chosen', // Class on the chosen item
dragClass: 'sortable-drag', // Class on the dragging item
// Multi-drag
multiDrag: false, // Enable multi-selection
selectedClass: 'sortable-selected', // Class on selected items
// Accessibility
enableAccessibility: true, // Keyboard nav + ARIA
// Events
onStart: (evt) => {},
onEnd: (evt) => {},
onAdd: (evt) => {},
onRemove: (evt) => {},
onUpdate: (evt) => {},
onSort: (evt) => {},
onChoose: (evt) => {},
onUnchoose: (evt) => {},
onClone: (evt) => {},
onChange: (evt) => {},
onMove: (evt, originalEvent) => {},
onSelect: (evt) => {},
onFilter: (evt) => {},
});
// Source list — items are cloned when dragged out
new Sortable(sourceList, {
group: { name: 'shared', pull: 'clone', put: false },
});
// Target list — accepts items from 'shared' group
new Sortable(targetList, {
group: { name: 'shared', pull: true, put: true },
});
sortable.toArray() // Get order as array of data-id values
sortable.sort(['c','a','b']) // Set order by data-id values
sortable.option('animation') // Get option value
sortable.option('animation', 300) // Set option value
sortable.destroy() // Remove instance and clean up
Sortable.active // Currently active Sortable instance
Sortable.dragged // Currently dragged element
Sortable.ghost // Ghost placeholder element
Sortable.clone // Cloned element from the most recent clone operation
Sortable.get(el) // Get Sortable instance by element
Sortable.closest(el, selector) // Find closest Sortable ancestor
// DOM helpers — Sortable.utils.* (note: distinct from the static surface above)
Sortable.utils.on(el, event, handler) // addEventListener; returns an unsubscribe fn
Sortable.utils.off(el, event, handler) // removeEventListener (symmetric to on)
Sortable.utils.index(el) // Element's index within its parent
Sortable.utils.insertAt(parent, el, index) // Insert el at a specific index in parent
Sortable.utils.closest(el, selector, ctx?) // Nearest ancestor matching selector, bounded by ctx
Sortable.utils.toggleClass(el, name, force?) // classList.toggle wrapper
Sortable.utils.clone(el) // Deep-clone an element (cloneNode(true))
import { Sortable, PluginSystem } from 'resortable';
import { registerAllPlugins } from 'resortable/plugins';
// Register all built-in plugins
registerAllPlugins();
const sortable = new Sortable(element, { animation: 150 });
sortable.usePlugin('AutoScroll');
sortable.usePlugin('Swap');
Built-in plugins: AutoScroll, MarqueeSelect, OnSpill, Swap.
Note: Multi-drag is built into the core — no plugin needed. Set
multiDrag: truein options. (TheMultiDragPluginv1-compat shim was removed in #34; see the migration guide.)
See the Plugin Development Guide for the plugin lifecycle, hook reference, and authoring patterns.
Full TypeDoc-generated API reference: jjeff.github.io/resortable/api/ (coming soon — TypeDoc-generated, deployed via GitHub Pages).
First-class wrappers for React, Vue, and Svelte are on the roadmap but not yet shipped. Resortable works today with any framework via the imperative new Sortable(element, options) API on a ref/useEffect-mounted element. See #44 for the v2.0 master roadmap, where framework-wrapper packages are tracked.
The repo ships with a curated set of nine standalone examples covering the v2 API surface — basic list, shared lists, kanban board, clone mode, swap, multi-drag, handle + filter, accessibility, and a custom plugin.
Live examples: https://jjeff.github.io/resortable/demo/examples/
To run them against the source locally, see ./examples/index.html (clone the repo and npm run dev, then open http://localhost:5173/examples/index.html).
npm install # Install dependencies
npm run dev # Start Vite dev server
npm run build # Build library (ESM, CJS, UMD)
npm run test # Run unit tests (Vitest)
npm run test:e2e # Run E2E tests (Playwright)
npm run type-check # TypeScript strict check
npm run lint # ESLint
See ARCHITECTURE.md for module documentation.
| Module | Purpose |
|---|---|
core/DragManager |
Drag interaction handling (HTML5 + Pointer Events) |
core/DropZone |
Container management and DOM operations |
core/EventSystem |
Type-safe event emitter |
core/GlobalDragState |
Cross-zone drag coordination |
core/GroupManager |
Group config and clone detection |
core/GhostManager |
Ghost/placeholder elements |
core/KeyboardManager |
Keyboard navigation |
core/SelectionManager |
Multi-item selection |
core/PluginSystem |
Plugin lifecycle management |
animation/AnimationManager |
FLIP-based animations |
MIT © 2025-2026 Jeff Robbins
Resortable is a TypeScript rewrite of SortableJS by Lebedev Konstantin and the SortableJS contributors. The original library pioneered the drag-and-drop patterns this project builds on, and the legacy source under legacy-sortable/ is consulted for v1 parity. See NOTICE for full attribution.