Skip to main content

Overview

The Svelte adapter provides functions and components that integrate drag and drop functionality into your Svelte 5 applications using runes and the modern Svelte API.

Installation

Install the Svelte package and its dependencies:
npm install @dnd-kit/svelte @dnd-kit/dom @dnd-kit/abstract
Requirements: Svelte 5.29.0 or higher

Getting Started

1

Add DragDropProvider

Wrap your application with the DragDropProvider component:
<script>
  import {DragDropProvider} from '@dnd-kit/svelte';
</script>

<DragDropProvider>
  <!-- Your app content -->
</DragDropProvider>
2

Create draggable components

Use the createDraggable function to make elements draggable:
<script>
  import {createDraggable} from '@dnd-kit/svelte';

  let {id, title} = $props();

  const {attach, isDragging} = createDraggable({
    id,
    data: {title},
  });
</script>

<div use:attach style:opacity={isDragging ? 0.5 : 1}>
  {title}
</div>
3

Create droppable zones

Use the createDroppable function to create drop targets:
<script>
  import {createDroppable} from '@dnd-kit/svelte';

  let {id, children} = $props();

  const {attach, isDropTarget} = createDroppable({id});
</script>

<div
  use:attach
  style:background-color={isDropTarget ? 'lightblue' : 'transparent'}
>
  {@render children?.()}
</div>
4

Handle drag events

Listen to events via the provider props:
<script>
  import {DragDropProvider} from '@dnd-kit/svelte';

  function handleDragEnd(event) {
    const {source, target} = event.operation;
    console.log('Dropped', source.id, 'on', target?.id);
  }
</script>

<DragDropProvider onDragEnd={handleDragEnd}>
  <!-- Your components -->
</DragDropProvider>

Complete Example

Here’s a full drag and drop implementation:
<!-- App.svelte -->
<script>
  import {DragDropProvider} from '@dnd-kit/svelte';
  import DraggableItem from './DraggableItem.svelte';
  import DroppableZone from './DroppableZone.svelte';

  let zones = $state({
    'zone-1': [
      {id: 'item-1', label: 'Item 1'},
      {id: 'item-2', label: 'Item 2'},
    ],
    'zone-2': [
      {id: 'item-3', label: 'Item 3'},
    ],
  });

  function handleDragEnd(event) {
    const {source, target} = event.operation;
    
    if (!target) return;

    // Remove from all zones
    for (const zoneId in zones) {
      zones[zoneId] = zones[zoneId].filter((item) => item.id !== source.id);
    }
    
    // Add to target zone
    zones[target.id] = [...zones[target.id], source.data];
  }
</script>

<DragDropProvider onDragEnd={handleDragEnd}>
  <div style="display: flex; gap: 16px; padding: 24px;">
    {#each Object.entries(zones) as [zoneId, items]}
      <DroppableZone id={zoneId} title={`Zone ${zoneId.split('-')[1]}`}>
        {#each items as item (item.id)}
          <DraggableItem id={item.id} label={item.label} />
        {/each}
      </DroppableZone>
    {/each}
  </div>
</DragDropProvider>
<!-- DraggableItem.svelte -->
<script>
  import {createDraggable} from '@dnd-kit/svelte';

  let {id, label} = $props();

  const {attach, isDragging, isDragSource} = createDraggable({
    id,
    data: {label},
  });
</script>

<div
  use:attach
  style:padding="16px"
  style:margin="8px"
  style:background-color={isDragging ? '#e3f2fd' : '#f5f5f5'}
  style:border={isDragSource ? '2px solid #2196f3' : '1px solid #ddd'}
  style:border-radius="4px"
  style:cursor="grab"
>
  {label}
</div>
<!-- DroppableZone.svelte -->
<script>
  import {createDroppable} from '@dnd-kit/svelte';

  let {id, title, children} = $props();

  const {attach, isDropTarget} = createDroppable({id});
</script>

<div
  use:attach
  style:min-height="200px"
  style:padding="16px"
  style:margin="8px"
  style:background-color={isDropTarget ? '#e8f5e9' : '#fafafa'}
  style:border="2px dashed #ccc"
  style:border-radius="8px"
>
  <h3>{title}</h3>
  {@render children?.()}
</div>

Sortable Lists

Create sortable lists with the createSortable function:
<script>
  import {DragDropProvider} from '@dnd-kit/svelte';
  import {createSortable} from '@dnd-kit/svelte/sortable';

  let items = $state([
    {id: '1', label: 'Item 1'},
    {id: '2', label: 'Item 2'},
    {id: '3', label: 'Item 3'},
    {id: '4', label: 'Item 4'},
  ]);

  function handleDragEnd(event) {
    const {source, target} = event.operation;
    
    if (!target || source.index === target.index) return;

    const newItems = [...items];
    const [removed] = newItems.splice(source.index, 1);
    newItems.splice(target.index, 0, removed);
    items = newItems;
  }
</script>

<DragDropProvider onDragEnd={handleDragEnd}>
  <div style="max-width: 400px; padding: 24px;">
    {#each items as item, index (item.id)}
      <SortableItem id={item.id} {index} label={item.label} />
    {/each}
  </div>
</DragDropProvider>
<!-- SortableItem.svelte -->
<script>
  import {createSortable} from '@dnd-kit/svelte/sortable';

  let {id, index, label} = $props();

  const {attach, isDragging, isDragSource} = createSortable({
    id,
    index,
    group: 'list',
    data: {label},
  });
</script>

<div
  use:attach
  style:padding="12px"
  style:margin="4px 0"
  style:background-color={isDragging ? '#e3f2fd' : 'white'}
  style:border={isDragSource ? '2px solid #2196f3' : '1px solid #ddd'}
  style:border-radius="4px"
  style:cursor="grab"
>
  {label}
</div>

Function APIs

createDraggable

The createDraggable function signature from /home/daytona/workspace/source/packages/svelte/src/core/draggable/createDraggable.svelte.ts:13:
function createDraggable<T extends Data = Data>(
  input: CreateDraggableInput<T>
): {
  draggable: Draggable<T>;
  isDragging: boolean;
  isDropping: boolean;
  isDragSource: boolean;
  attach: (node: HTMLElement) => () => void;
  attachHandle: (node: HTMLElement) => () => void;
}

interface CreateDraggableInput<T extends Data = Data> {
  id: string;
  data?: T;
  disabled?: boolean;
  modifiers?: Modifier[];
  sensors?: Sensor[];
  plugins?: Plugin[];
  alignment?: Alignment;
}

createDroppable

The createDroppable function signature from /home/daytona/workspace/source/packages/svelte/src/core/droppable/createDroppable.svelte.ts:13:
function createDroppable<T extends Data = Data>(
  input: CreateDroppableInput<T>
): {
  droppable: Droppable<T>;
  isDropTarget: boolean;
  attach: (node: HTMLElement) => () => void;
}

interface CreateDroppableInput<T extends Data = Data> {
  id: string;
  data?: T;
  disabled?: boolean;
  accept?: string | string[];
  type?: string;
  collisionDetector?: CollisionDetector;
}

createSortable

The createSortable function signature from /home/daytona/workspace/source/packages/svelte/src/sortable/createSortable.svelte.ts:14:
function createSortable<T extends Data = Data>(
  input: CreateSortableInput<T>
): {
  sortable: Sortable<T>;
  isDragging: boolean;
  isDropping: boolean;
  isDragSource: boolean;
  isDropTarget: boolean;
  attach: (node: HTMLElement) => () => void;
  attachHandle: (node: HTMLElement) => () => void;
  attachSource: (node: HTMLElement) => () => void;
  attachTarget: (node: HTMLElement) => () => void;
}

interface CreateSortableInput<T extends Data = Data> {
  id: string;
  group: string;
  index: number;
  data?: T;
  disabled?: boolean;
  type?: string;
  accept?: string | string[];
  modifiers?: Modifier[];
  sensors?: Sensor[];
  plugins?: Plugin[];
  collisionDetector?: CollisionDetector;
  collisionPriority?: number;
  transition?: SortableTransition;
  alignment?: Alignment;
}

Advanced Features

Drag Handles

Use separate elements for dragging:
<script>
  import {createDraggable} from '@dnd-kit/svelte';

  let {id, title} = $props();

  const {attach, attachHandle, isDragging} = createDraggable({id});
</script>

<div use:attach style:opacity={isDragging ? 0.5 : 1}>
  <div use:attachHandle style:cursor="grab" style:padding="8px"></div>
  <div>{title}</div>
</div>

Modifiers

Constrain drag behavior:
<script>
  import {DragDropProvider} from '@dnd-kit/svelte';
  import {restrictToVerticalAxis} from '@dnd-kit/abstract/modifiers';

  const modifiers = [restrictToVerticalAxis];
</script>

<DragDropProvider {modifiers}>
  <!-- Content -->
</DragDropProvider>

Sensors

Customize activation:
<script>
  import {DragDropProvider} from '@dnd-kit/svelte';
  import {PointerSensor} from '@dnd-kit/dom';

  const sensors = [
    new PointerSensor({
      activationConstraint: {
        delay: 250,
        tolerance: 5,
      },
    }),
  ];
</script>

<DragDropProvider {sensors}>
  <!-- Content -->
</DragDropProvider>

Type Safety

Define custom data types:
<script lang="ts">
  import {createDraggable} from '@dnd-kit/svelte';

  interface TaskData {
    id: string;
    title: string;
    priority: 'low' | 'medium' | 'high';
  }

  let {task}: {task: TaskData} = $props();

  const {attach, isDragging} = createDraggable<TaskData>({
    id: task.id,
    data: task,
  });
</script>

<div use:attach>
  <h3>{task.title}</h3>
  <span>Priority: {task.priority}</span>
</div>

Svelte 5 Runes

The adapter uses Svelte 5’s runes for reactivity:
<script>
  import {createDraggable} from '@dnd-kit/svelte';

  // Props are reactive using $props()
  let {id} = $props();

  // State is reactive using $state()
  let isDisabled = $state(false);

  // Derived state uses $derived()
  let label = $derived(`Item ${id}`);

  const {attach, isDragging} = createDraggable({
    id,
    disabled: isDisabled,
    data: {label},
  });

  // Effects use $effect()
  $effect(() => {
    console.log('Dragging state changed:', isDragging);
  });
</script>

<div use:attach>
  {label}
  <button onclick={() => isDisabled = !isDisabled}>
    {isDisabled ? 'Enable' : 'Disable'}
  </button>
</div>

Actions (use:directive)

All attach functions return cleanup functions, making them perfect for use with Svelte actions:
<script>
  const {attach, attachHandle} = createDraggable({id: 'card'});
</script>

<!-- The attach action automatically handles cleanup -->
<div use:attach>
  <div use:attachHandle>Drag Handle</div>
  <div>Content</div>
</div>

Best Practices

  • Stable IDs: Use consistent, unique IDs for draggables and droppables
  • Key blocks: Always use proper keys in {#each} blocks for sortables
  • Runes: Leverage Svelte 5 runes for reactive inputs
  • Actions: Use use:attach directives for clean element binding
  • Cleanup: Actions automatically handle cleanup when components unmount

Next Steps

Sortable Lists

Build sortable lists with animations

Multiple Containers

Drag between multiple containers

Sensors

Configure interaction methods

Events

Handle drag and drop events

Build docs developers (and LLMs) love