Drag & Drop
@domphy/dnd adds sortable lists and drag & drop to Domphy: reorder, transfer between lists, multi-drag, and drop animations.
This package depends on @formkit/drag-and-drop (MIT, zero-dependency, framework-agnostic) and adds a thin Domphy adapter — the same way FormKit's own React/Vue/Solid adapters wrap the engine. The full FormKit API is re-exported.
Install
npm install @domphy/dnd @domphy/core<script src="https://unpkg.com/@domphy/dnd/dist/dnd.global.js"></script>@domphy/core is a peer dependency.
Live Example
Usage
Apply dragDrop(state, config?) to the list container with $, and render the children reactively from the same state with a stable _key:
import { toState } from "@domphy/core"
import { dragDrop } from "@domphy/dnd"
const items = toState([
{ id: 1, label: "Write docs" },
{ id: 2, label: "Ship it" },
])
const App = {
ul: (l) => items.get(l).map((item) => ({ li: item.label, _key: item.id })),
$: [dragDrop(items)],
}Dragging reorders the DOM, FormKit calls setValues → the items state updates → Domphy re-renders the keyed children in the new order. The _key is required so the reorder maps to the right nodes.
Config & plugins
dragDrop(state, config) forwards config to FormKit's ParentConfig, and the whole FormKit API (plugins like animations, insert, dropOrSwap, group transfer) is re-exported. Animations are on by default:
import { dragDrop } from "@domphy/dnd"
// animated by default; transfer items between any lists sharing a group
{ ul: (l) => ..., $: [dragDrop(items, { group: "todos" })] }Give two lists the same group to transfer items between them. Use animated: false to opt out of animations. Touch and synthetic-drag handling come from FormKit — see the FormKit DnD docs for the full config.
Keyboard
The FormKit engine is pointer-only. Add keyboardSort() to each item for the keyboard path WCAG 2.2 SC 2.5.7 requires — space/enter picks an item up, arrows move it, escape cancels, and every step is announced:
import { dragDrop, keyboardSort } from "@domphy/dnd"
const sortItem = keyboardSort(items)
const App = {
ul: (l) =>
items.get(l).map((item, index) => ({
li: item.label,
_key: item.id,
$: [sortItem(index)],
})),
$: [dragDrop(items)],
}See Accessibility.
Cleanup
The FormKit registration lives in a per-node behavior() instance; on element removal its destroy runs FormKit's tearDown(), disconnects the setup MutationObserver upstream leaves running, and clears the entry from FormKit's parents registry.