Skip to content

Svelte

sh
npm install @anil-labs/dnd-svelte

Works with Svelte 4 and Svelte 5 (runes) alike.

The use:draggable action

You render the list with {#each}; the action wires the engine to the container:

svelte
<script lang="ts">
  import { draggable } from '@anil-labs/dnd-svelte'
  import '@anil-labs/dnd-core/styles.css'

  let items = $state([
    { id: 1, text: 'Apple' },
    { id: 2, text: 'Banana' },
    { id: 3, text: 'Cherry' },
  ])
</script>

<ul
  use:draggable={{
    items,
    onItemsChange: (v) => (items = v),
    group: 'tasks',
    animation: 200,
    onEnd: (e) => console.log('dropped', e.oldIndex, '→', e.newIndex),
  }}
>
  {#each items as item, i (item.id)}
    <li data-dnd-index={i} class="dnd-item">{item.text}</li>
  {/each}
</ul>

Three rules for the markup:

  1. Items are keyed direct children ((item.id)) so Svelte moves nodes instead of recreating them.
  2. Each item carries data-dnd-index={i} matching its array position.
  3. Each item has the dnd-item class (cursor + touch-action).

Options

items and onItemsChange plus everything in the options reference — including all event callbacks (onStart, onEnd, onAdd, onRemove, onUpdate, onChange, onSelectionChange, onKeyboardMove, …).

The action's update runs whenever the options object changes, so reactive values Just Work:

svelte
<ul use:draggable={{ items, onItemsChange: (v) => (items = v), disabled: locked }}>

Svelte 4

Identical, with store/let state instead of runes:

svelte
<script>
  import { draggable } from '@anil-labs/dnd-svelte'
  let items = [{ id: 1, text: 'Apple' }]
</script>

<ul use:draggable={{ items, onItemsChange: (v) => (items = v), animation: 200 }}>
  {#each items as item, i (item.id)}
    <li data-dnd-index={i} class="dnd-item">{item.text}</li>
  {/each}
</ul>

Selection & keyboard state

Track them via callbacks and render whatever you like:

svelte
<script lang="ts">
  let selected = $state<number[]>([])
</script>

<ul
  use:draggable={{
    items,
    onItemsChange: (v) => (items = v),
    multiDrag: true,
    onSelectionChange: (e) => (selected = e.indices),
  }}
>
  {#each items as item, i (item.id)}
    <li data-dnd-index={i} class="dnd-item" class:picked={selected.includes(i)}>
      {item.text}
    </li>
  {/each}
</ul>

For keyboard mode add tabindex="0" to items (or rely on the engine's announcements only); the dnd-keyboard-active outline is applied via onKeyboardStateChange if you mirror it into a class the same way.

Released under the MIT License.