npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

resortable

v2.1.5

Published

Modern TypeScript rewrite of Sortable.js - reorderable drag-and-drop lists

Readme

Resortable

Modern TypeScript rewrite of Sortable.js — reorderable drag-and-drop lists.

npm version npm downloads CI gzip size TypeScript License: MIT

Migrating from Sortable.js? See the migration guide for the option/plugin/breaking-change delta.

Contents: Features · Install · Quick start · Options · Methods · Static API · Plugins · Examples · Development · Architecture

Documentation

Full docs hub: jjeff.github.io/resortable · folder index: docs/

| Guide | What's inside | | --- | --- | | API Reference | TypeDoc — every exported class, interface, option, and type | | Migration from Sortable v1 | Option renames, plugin changes, and breaking-change delta | | Plugin Development | Plugin lifecycle, hook reference, and authoring patterns | | Accessibility | Keyboard contract, ARIA attributes, and the WCAG 2.1 AA audit |

Live: Examples · Showcase · Playground  •  Project: Architecture · Contributing · Security · Changelog

Features

  • Drag & Drop — HTML5 Drag API + Pointer Events for mouse, touch, and pen
  • Cross-Container — Drag items between lists with shared groups
  • Clone Supportgroup.pull: 'clone' to copy items instead of moving
  • Animations — FLIP-based 60fps reorder animations
  • Accessibility — Full keyboard navigation, ARIA attributes, screen reader support. WCAG 2.1 AA verified via an automated axe-core audit in CI; see docs/accessibility.md for the keyboard contract and ARIA reference.
  • Multi-Drag — Ctrl+Click / Shift+Click selection, drag multiple items together
  • Controlled modecontrolled: true reports the drop as intent (newIndex/newIndexes + target zone) and never mutates your DOM, so React/Vue/state-owned lists stay the single source of truth
  • React hookresortable/react ships a useSortable hook (1.4 kB, built on controlled mode); Vue/Svelte wrappers are on the roadmap
  • Flexible drop targetingemptyInsertThreshold and hitArea let a list accept drops thrown at the empty space or surrounding region around it, not just its own box
  • Plugin System — AutoScroll, Swap plugins (extensible architecture)
  • TypeScript — Strict types with full IntelliSense support, zero runtime dependencies
  • Small — ~20 kB gzipped (19.8 kB ESM)
  • Cross-browser tested — 299 unit tests (Vitest) + 182 end-to-end tests (Playwright) exercised across Chromium, Firefox, WebKit, and mobile emulation (iOS Safari + Android Chrome), run on Linux, Windows, and macOS in CI

Installation

npm (recommended)

npm install resortable

CDN (UMD)

The UMD bundle exposes a window.Sortable global and works without a bundler. Version-pin to avoid surprise breaking changes:

<!-- unpkg — @2 tracks the latest 2.x (major-pinned, no surprise breaking changes) -->
<script src="https://unpkg.com/resortable@2/dist/sortable.umd.js"></script>

<!-- or jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/resortable@2/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.

Quick Start

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}`);
  }
});

Options

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
  direction: 'vertical',             // 'vertical' | 'horizontal' list axis
  // swapThreshold: 0.5,              // Min overlap fraction (0-1) before an item swaps;
                                     // omit for the default (no overlap gate)

  // Drop targeting
  emptyInsertThreshold: 5,           // Px around an empty list that still counts as a drop
  hitArea: '.row',                   // Selector for a surrounding region whose drops this
                                     // list also claims, inserting at the nearest end

  // 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

  // State ownership
  controlled: false,                 // Report intent via events, never mutate the DOM

  // 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) => {},
});

Shared Groups / Clone

// 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 },
});

Methods

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

Static API

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))

Plugins

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: true in options. (The MultiDragPlugin v1-compat shim was removed in #34; see the migration guide.)

Authoring custom plugins

See the Plugin Development Guide for the plugin lifecycle, hook reference, and authoring patterns.

API Reference

Full TypeDoc-generated API reference: jjeff.github.io/resortable/api/.

Framework wrappers

React ships today as a first-class hook: import { useSortable } from 'resortable/react'. It is built on controlled mode, so your component state stays the source of truth — the hook reports reorders as intent and never mutates React-owned DOM:

import { useSortable } from 'resortable/react';

function List({ items, setItems }) {
  const { ref } = useSortable<HTMLUListElement>({
    animation: 150,
    onSort: (intent) => setItems(reorder(items, intent)),
  });
  return (
    <ul ref={ref}>
      {items.map((i) => <li key={i.id} data-id={i.id}>{i.label}</li>)}
    </ul>
  );
}

Vue and Svelte wrappers are on the roadmap. Any other framework works today via the imperative new Sortable(element, options) API on a ref/useEffect-mounted element. See #44 for the v2.0 master roadmap, where the remaining framework-wrapper packages are tracked.

Examples

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).

Development

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

Setup, conventions, and the PR checklist live in CONTRIBUTING.md.

Architecture

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 |

License

MIT © 2025-2026 Jeff Robbins

Acknowledgments

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.