Skip to content
Domphy

Animations

The animations() plugin adds CSS transition animations to sort and transfer operations. Items slide into their new positions after a drop rather than snapping instantly.

dragDrop() includes animations by default — no configuration needed.

Basic Setup

Animations are on automatically:

import { dragDrop } from "@domphy/dnd"
import { toState } from "@domphy/core"
import { themeColor, themeSpacing } from "@domphy/theme"

const items = toState([
  { id: 1, label: "First" },
  { id: 2, label: "Second" },
  { id: 3, label: "Third" },
])

const SortableList = {
  ul: (l) =>
    items.get(l).map((item) => ({
      li: item.label,
      _key: item.id,
      style: {
        padding: themeSpacing(3),
        marginBottom: themeSpacing(2),
        backgroundColor: (cl) => themeColor(cl, "shift-2"),
        borderRadius: themeSpacing(2),
        cursor: "grab",
        userSelect: "none",
      },
    })),
  $: [dragDrop(items)],
  style: { listStyle: "none", padding: "0" },
}

To disable: dragDrop(items, { animated: false }).

animations() uses the Web Animations API internally and cleans up after itself.

Configuration

To customize animation duration or easing, disable the default and pass the plugin explicitly:

import { dragDrop, animations } from "@domphy/dnd"

dragDrop(items, {
  animated: false,  // disable default
  plugins: [animations({ duration: 200, easing: "ease-in-out" })],
})

Examples:

animations({ duration: 100, easing: "ease" })
animations({ duration: 300, easing: "cubic-bezier(0.22, 1, 0.36, 1)" })
animations({ duration: 50, easing: "linear" })

Animations in Multi-Container

Animations are on by default in all dragDrop() calls — including multi-container setups:

const GROUP = "board"

const ListA = {
  ul: (l) => stateA.get(l).map((item) => ({ li: item.label, _key: item.id })),
  $: [dragDrop(stateA, { group: GROUP })],
}

const ListB = {
  ul: (l) => stateB.get(l).map((item) => ({ li: item.label, _key: item.id })),
  $: [dragDrop(stateB, { group: GROUP })],
}

Pass animated: false on any individual list to opt it out of animations.

Combining Plugins

animations() is included by default. To add other plugins alongside it, append them to pluginsanimations() prepends automatically:

import { dragDrop, dropOrSwap } from "@domphy/dnd"

// Other plugins alongside default animation:
dragDrop(items, {
  plugins: [dropOrSwap()],
})

To use a custom animation config alongside other plugins, disable the default first:

import { dragDrop, animations, dropOrSwap } from "@domphy/dnd"

dragDrop(items, {
  animated: false,
  plugins: [
    animations({ duration: 180 }),
    dropOrSwap(),
  ],
})

Visual Feedback with CSS Classes

animations() handles the reorder transition (enabled by default). For immediate visual feedback during the drag, use the class config options. These are applied directly to DOM elements by FormKit, so they must live in a stylesheet:

dragDrop(items, {
  draggingClass: "item-dragging",          // the item being dragged
  dropZoneClass: "item-drop-target",       // item currently hovered over
  dropZoneParentClass: "list-drop-active", // parent list being hovered over
})
const sheet = document.createElement("style")
sheet.textContent = `
  .item-dragging     { opacity: 0.4; }
  .item-drop-target  { outline: 2px dashed currentColor; outline-offset: -2px; }
  .list-drop-active  { background: rgba(0,0,0,.04); border-radius: 8px; }
`
document.head.appendChild(sheet)

Synthetic Drag Classes

On touch and pointer devices, FormKit uses a synthetic drag instead of the native HTML5 drag API. A separate set of class options controls the appearance during synthetic drags:

dragDrop(items, {
  synthDraggingClass: "synth-dragging",
  synthDropZoneClass: "synth-drop-target",
  synthDropZoneParentClass: "synth-list-active",
})

Use the same CSS class names for both native and synthetic if you want consistent feedback regardless of input device:

const CLS = "item-dragging"

dragDrop(items, {
  draggingClass: CLS,
  synthDraggingClass: CLS,
})

Respecting Reduced Motion

Use animated: false to disable animations for users who prefer reduced motion:

const prefersReducedMotion =
  window.matchMedia("(prefers-reduced-motion: reduce)").matches

dragDrop(items, {
  animated: !prefersReducedMotion,
})