Drag-and-drop sorting for Vanilla JavaScript, React, Vue, and Svelte, built on one TypeScript core with zero runtime dependencies. Build sortable lists, grids, kanban boards, and nested trees with controlled application state.
npm package · English docs · 한국어 가이드
The animation shows real React Playground interactions: emptying and refilling a list between fixed slots, changing drag start areas, comparing accepted and rejected drop styles, updating a child list, and moving a complete subtree. By default, drag a card's left handle to move it and select its body text to copy it. The Drag start areas example also demonstrates title-only and whole-card dragging.
| Capability | What to try in the Playground |
|---|---|
| Sortable lists and kanban boards | Reorder items, transfer them between lists, or copy them to another list. |
| Drag start areas and text selection | Compare handle, title-only, and whole-card dragging; use handle mode to select and copy body text. |
| Multi-drag | Command/Ctrl-click handles, or use Shift for a range, then move the selected items together. |
| Sorting thresholds | Change the shaded target region to see when a drag changes order; this differs from the distance needed to start dragging. |
| Grid and Swap Grid | Grid inserts and shifts items. Swap Grid exchanges only the dragged and highlighted cells. |
| Nested lists and Tree | Per-list state control updates separate arrays in React, Vue, and Svelte; Vanilla demonstrates direct DOM updates. Subtree movement preserves descendants when a node changes parent. Both include structure diagrams and step-by-step instructions. |
| Drag feedback | Compare accepted/rejected drop styles and their CSS; customize animation, placeholders, skeleton feedback, and auto-scroll. |
Vanilla uses no framework peer. React, React DOM, Vue, and Svelte integrations
use optional peers (react, react-dom, vue, and svelte).
The 0.1.3 changes improve selection cleanup, elapsed-time and RTL auto-scroll, feedback outside valid drop areas, and footer boundaries after a list becomes empty. Playground Reset also clears the Shift-range anchor. The examples now compare drag start areas and accepted/rejected CSS feedback. See the changelog for the full change list and the capture details for the GIF scenarios.
npm install comins-sortableInstall the peers used by your chosen adapter: React >=18.2 <20 with React DOM
>=18.2 <20, Vue >=3.5 <4, or Svelte >=5 <6. Vanilla needs no framework peer.
Use the repository Playground below to try the examples.
The Playground currently provides 25 routes per adapter, covering Vanilla JavaScript, React, Vue, and Svelte. Clone the repository to run the examples:
git clone https://github.com/kim1124/comins-sortable.git
cd comins-sortable
npm ci --ignore-scripts
npm run devOpen the React simple-sorting example. The development server uses port 4003.
Use Reset to restore the example and clear multi-selection, including its Shift-range anchor, in all four adapters.
Comins Sortable is controlled: the application owns the item array and commits
each proposed order through onItemsChange.
import { useState } from 'react';
import { SortableArea, SortableRoot } from 'comins-sortable/react';
import 'comins-sortable/styles.css';
type Item = { id: string; label: string };
export function TaskList() {
const [items, setItems] = useState<Item[]>([
{ id: 'task-1', label: 'Plan' },
{ id: 'task-2', label: 'Build' },
]);
return (
<SortableRoot<Item>>
<SortableArea
areaId="tasks"
items={items}
itemKey="id"
handle=".task-handle"
onItemsChange={(nextItems) => setItems([...nextItems])}
>
{(item) => (
<div>
<button
type="button"
className="task-handle"
aria-label={`Drag ${item.label}`}
style={{ touchAction: 'none', userSelect: 'none', WebkitUserSelect: 'none' }}
>
⠿
</button>
<span>{item.label}</span>
</div>
)}
</SortableArea>
</SortableRoot>
);
}The handle confines pointer activation to the button, so the label remains available for text selection. A labelled button does not add keyboard sorting; this module's sorting interaction is pointer-based. See the handle and text-selection guide for styling and multi-selection behavior.
- Documentation index
- English Quick Start
- Korean Quick Start
- English Public API Reference
- 한글 Public API 레퍼런스
- All English feature guides
- 모든 한글 기능 가이드
createSortableTree is a framework-neutral, schema-adapted public API. It maps
one immutable tree value to sortable areas and folds controlled area updates
back into that value without owning a component hierarchy.
import { createSortableTree } from 'comins-sortable/core';
const tree = createSortableTree<Node>({
rootAreaId: 'root',
getNodeId: (node) => node.id,
getChildren: (node) => node.children,
withChildren: (node, children) => ({ ...node, children }),
getChildrenAreaId: (node) => `children-${node.id}`,
});
const areas = tree.getAreas(nodes);
const nextNodes = tree.updateArea(nodes, areaId, nextItems);React, Vue, and Svelte controlled Areas use updateArea from their item update
callback. applyChange accepts the typed FrameworkSortableChange emitted by
framework Roots when a consumer needs to fold a whole transaction at once.
Every Area accepts placeholder. className adds consumer classes beside the
stable comins-sortable__placeholder class. The optional skeleton preset is
drag feedback only; it is not a general loading-skeleton API.
const areaOptions = {
placeholder: {
className: 'project-drop-placeholder',
preset: 'skeleton' as const,
},
};Consumers can style either their class or these public CSS variables:
--comins-sortable-placeholder-background--comins-sortable-placeholder-border--comins-sortable-placeholder-border-radius--comins-sortable-placeholder-opacity--comins-sortable-placeholder-skeleton-base--comins-sortable-placeholder-skeleton-highlight--comins-sortable-placeholder-skeleton-duration
For rejected targets and the dragged preview, use
--comins-sortable-rejection-outline and
--comins-sortable-rejection-outline-offset on a shared container. The
Drop feedback styles example compares these with accepted-position styling.
Import comins-sortable/styles.css to enable the preset. Reduced-motion mode
disables the skeleton animation.
Insertion feedback hides outside a valid drop destination and returns on reentry. During a move, the hidden placeholder keeps its layout space to preserve the list's scroll range.
Automated tests cover Chromium, Firefox, and WebKit. Sorting uses pointer input; keyboard reordering is not implemented. See the browser verification scope for the latest local checks and the separately recorded native Safari and device coverage.
Known Playground issue: Safari 26.6.2 can leave body text selected after a title-only drag, although the item order updates. Use the dedicated handle when body-text selection and copying matter. This issue remains open; see the handle guide.
