Skip to content

makeDraggable

makeDraggable turns an element into a draggable item. It must be used inside a DnDProvider. Pass a template ref (or useTemplateRef) and optionally options and a payload.

Signature

ts
makeDraggable(ref, options?, payload?): IMakeDraggableReturnType

Overloads:

  • makeDraggable(ref) — no options, no payload.
  • makeDraggable(ref, options?) — optional options object.
  • makeDraggable(ref, payload) — if the second argument is a function, it is treated as the payload (options are empty).
  • makeDraggable(ref, options, payload?) — options and optional payload.

ref must be a ref to an HTML element (or component root element). The element is registered on mount and unregistered on unmount. Native dragstart / drag / dragend are prevented so the browser doesn't start its own drag.


Return value

PropertyTypeDescription
selectedWritableComputedRef<boolean>Whether this element is in the current selection. Can be read and written (e.g. for checkboxes).
isDraggingComputedRef<boolean>true while this element is being dragged.
isAllowedComputedRef<boolean>true when this element is a valid drag target (visible, not disabled, groups match with currently dragging items).
isDragOverComputedRef<IPlacement | undefined>When another item is dragged over this one: placement flags { top, right, bottom, left, center }. undefined when not hovered. Use for sort indicators and drop preview.

Options

Extends base options (shared with droppable/selection/constraint):

OptionTypeDescription
disabledboolean | Ref<boolean>When true, the element is not draggable.
groupsstring[] | Ref<string[]>Groups for matching with droppables (same group = can drop).

Draggable-specific options:

OptionTypeDescription
idstringStable identity for this draggable. Required when using virtual lists (e.g. useVirtualList) — see Virtual lists. Optional everywhere else: if omitted, a random id is generated automatically.
dragHandlestring | Ref<string>CSS selector for the handle. Only pointer events on this descendant start a drag. If omitted, the whole element is the handle.
modifier{ keys: string[], method: 'every' | 'some' } or refsKey(s) that must be pressed to allow drag (e.g. ['AltLeft'] with method: 'every').
activationIDragActivationOptionsWhen drag starts: distance (pixels or { x, y, condition }) and/or delay (ms). See Activation.
eventsIDraggableEventsCallbacks for drag lifecycle and hover. See Events.
renderComponentCustom Vue component used in the overlay instead of cloning the node.
placementMarginsIPlacementMargins{ top?, right?, bottom?, left? } in pixels. Used when this element is also a droppable: defines the "center" zone for placement.

Payload

payload is an optional function that describes the item position and source list — needed for the suggestSort / suggestSwap / suggestCopy helpers:

ts
type TDraggablePayload<T = any> = () => [index: number, items: T[]];
  • index — index of this item in the list.
  • items — the full list (array).

The library calls this when building event.draggedItems:

ts
interface IDragItem<T = unknown> {
  index: number;   // position in items
  item: T;         // the actual data: items[index]
  items: T[];      // reference to the source array
  data?: unknown;  // custom data from the `data` option
}

For arbitrary custom data (without a list), use the data option instead:

ts
makeDraggable(ref, { data: () => myCard })
// → event.draggedItems[0].data === myCard

You can combine both — payload for the helpers, data for custom metadata.


Activation

activation controls when a pointer down becomes a drag:

  • distance — movement threshold in pixels. Can be a number (same for x and y), or { x?, y?, condition? }. condition: 'any' = drag if either axis exceeds, 'both' = both must exceed.
  • delay — time in ms the pointer must be down before drag starts.

Use these to avoid accidental drags (e.g. require 5px move or 200ms hold).


Events

events can include:

On the dragged element itself (this draggable):

  • onSelfDragStart — drag started (this element is being dragged).
  • onSelfDragMove — drag move.
  • onSelfDragEnd — drag ended (dropped or cancelled).
  • onSelfDragCancel — drag cancelled (e.g. Escape).

When another element is dragged over this one (this draggable is under the cursor):

  • onDragStart, onDragMove, onDragEnd, onDragCancel.
  • onHover — cursor entered this draggable.
  • onLeave — cursor left this draggable.

Every event receives IDragEvent:

ts
interface IDragEvent<DragT = unknown, ZoneT = unknown> {
  draggedItems: IDragItem<DragT>[];              // all dragged items (multi-drag)
  dropZone: IDropZoneContext<ZoneT> | undefined; // zone under cursor (items + placement + data)
  hoveredDraggable: IHoveredDraggableContext | undefined; // hovered draggable inside the zone
  helpers: IHelpers;                             // low-level array ops + high-level suggest* presets
  provider: IDnDProviderExternal;                // access to provider state (pointer, state, etc.)
}

For simple cases you only need event.draggedItems, event.dropZone, and for sorting, event.hoveredDraggable. Use event.helpers.suggestSort() to compute new array state without writing sort logic manually.


Example

vue
<script setup lang="ts">
  import { useTemplateRef } from 'vue';
  import { makeDraggable } from '@vue-dnd-kit/core';

  const itemRef = useTemplateRef<HTMLElement>('itemRef');

  makeDraggable(itemRef, {
    dragHandle: '.handle',
    activation: { distance: 5 },
    events: {
      onSelfDragStart: (e) => console.log('drag start', e.draggedItems),
      onSelfDragEnd: (e) => console.log('drag end', e.draggedItems),
    },
  });
</script>

<template>
  <div ref="itemRef" tabindex="0">
    <span class="handle">⋮⋮</span>
    <span>Item</span>
  </div>
</template>

With payload (for lists / drop handlers that need index and items):

ts
const items = ref([1, 2, 3]);
makeDraggable(itemRef, { dragHandle: '.handle' }, () => [0, items.value]);

// later in onDrop:
// e.draggedItems[0].index, e.draggedItems[0].item, e.draggedItems[0].items

Using return values (e.g. for styling and sort indicators):

vue
<script setup lang="ts">
  const itemRef = useTemplateRef<HTMLElement>('itemRef');
  const { isDragging, isDragOver, selected } = makeDraggable(itemRef, { ... });
</script>

<template>
  <div
    ref="itemRef"
    :class="{
      'opacity-50': isDragging,
      'border-t-2': isDragOver?.top,
      'border-b-2': isDragOver?.bottom,
    }"
  >
    Item
  </div>
</template>

Virtual lists

When using a virtual list (e.g. useVirtualList from VueUse), items outside the viewport are unmounted and remounted as new DOM elements when they scroll back into view. Without a stable id, the library loses track of which element is being dragged — the remounted element gets a fresh registration and can incorrectly appear as a hover target, and dropping after a scroll-away has no effect.

Pass id equal to your data item's own identifier:

ts
// ❌ Without id — broken in virtual lists
makeDraggable(el, { groups: ['item'] }, () => [index, items.value])

// ✅ With id — correct in virtual lists
makeDraggable(el, { id: item.id, groups: ['item'] }, () => [index, items.value])

How it works: the id is written to the element's data-dnd-kit-draggable attribute on mount. When the element remounts, the library finds the matching entry in the drag session by that id and transfers it to the new DOM node. The old detached element still carries the attribute in memory, so even stale references can resolve to the current drag state. As a result:

  • isDragging stays true on the remounted element.
  • The item is not treated as a hover target for other draggables.
  • Dropping after a scroll-away fires onDrop correctly.

Outside of virtual lists id is optional — a random id is generated automatically per component instance.


See also

Released under the MIT License.