Docs/API

useDnD and monitors

The drag state and its events, anywhere under the provider — and the provider itself, handed to another Vue app.

Live

Loading the demo…

<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
import TokenScene from './TokenScene.vue';
import StateInspector from './StateInspector.vue';
</script>

<template>
  <DnDProvider>
    <div class="demo">
      <TokenScene />
      <StateInspector />
    </div>
  </DnDProvider>
</template>

<style scoped>
.demo {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 16px;
}
</style>

useDnD

function useDnD(): DnDState;

Read-only drag state for custom UI (valid-target highlighting, spinners, progress).

const { status, active, over, selection, cancel } = useDnD();
const dragging = computed(() => status.value === 'dragging');

The state of the provider's drag, reactive. active and over are cleared as soon as the drop is decided, while the overlay still lands.

The drag state ends when the drop is decided: active, over and pointer read null (and droppables stop accepting) while the overlay still animates into place. Derived UI never mixes the old drag with the new data. While an async onBeforeDrop decides, the state is kept as it was at release, but the page is out of drag mode: its own cursor and text selection are back (a confirmation dialog works normally), and the overlay waits where it was dropped.

status is 'dragging' while a drag runs, else 'dropping' while a drop is decided or an overlay still lands, else 'idle' ('activating' and 'selecting' aside). A new drag doesn't wait for the landings: it starts at once, and they finish around it.

Readonly<Ref<DnDStatus>>

What the provider is doing (see DnDStatus).

Readonly<Ref<Active | null>>

The running drag; null without one, and as soon as its drop is decided.

Readonly<Ref<Over | null>>

The drop target under the drag; null over nothing.

Readonly<Ref<Coordinates | null>>

Pointer in viewport coordinates, after modifiers; null without a drag.

Readonly<Ref<Coordinates | null>>

Where the dragged box's top-left corner is in over, live: DnDEvent.position on every move. null over nothing. An overlay component reads it to show where it would land (a time on a calendar).

{ progress: Readonly<Ref<number>> }

progress: how far a press has come toward its activation constraint, from 0 to 1, while status is 'activating': e.g. a ring that fills during a long press.

Readonly<Ref<boolean>>

Auto-scroll is moving a container.

DnDSelection

The selected draggables (see DnDSelection).

() => void

Cancels the drag (it flies back), or the pending activation.

Selection

The selected draggables, by id: a drag of one of them carries them all (multi-drag). Kept by id, a selection survives unmounts (virtual lists).

ReadonlySet<Key>

The selected ids, reactive.

(id: Key) => boolean

Whether id is selected.

(...ids: Key[]) => void

Selects these ids.

(...ids: Key[]) => void

Deselects these ids.

(id: Key, force?: boolean) => void

Selects id or deselects it; force: true selects it, false deselects it.

(ids: Iterable<Key>) => void

Selects these ids and no others.

() => void

Deselects every id.

useDnDMonitor

function useDnDMonitor(monitor: DnDMonitor): void;

Subscribes to the events of every drag under the provider, for as long as the calling component (or effect scope) lives.

useDnDMonitor({
  onDragEnd: (e) => (e.dropped ? save(e) : undefined),
  onBeforeDrop: async (e) => ((await confirm()) ? 'accept' : 'reject'),
});

Your monitors are asked about a drop (onBeforeDrop) before the target is. The payloads are on the events page.

Hooks for every drag under the provider (useDnDMonitor).

(event: DnDEvent) => void

A drag started, after its activation.

(event: DnDEvent) => void

The drag moved (once per frame at most).

(event: DnDEvent) => void

The target under the drag changed.

(event: DnDEvent) => MaybePromise<BeforeDropResult>

Runs before the drop is committed (see BeforeDropResult): 'reject' refuses it, ids accept a subset.

(event: DnDEndEvent) => void

A notification once the drop is done: sortable lists have run their onDrop and written it.

(event: DnDEvent) => void

The drag was cancelled, or its drop refused: the items fly back.

(event: DnDEvent) => void

onBeforeDrop answered 'continue': there was no drop, and the drag goes on.

(event: DnDEvent | DnDEndEvent) => void

The overlay of an ended drag has landed: after its drop animation (or the flight back of a cancel), with the event onDragEnd (a DnDEndEvent) or onDragCancel got. The next drag may have started meanwhile: drags don't wait for landings.

(event: KeyboardEvent, context: KeyboardMoveContext) => MaybePromise<Coordinates | undefined>

Keyboard drags: next pointer position for an arrow key (e.g. the next sortable slot).

What getKeyboardCoordinates is asked with, for an arrow key.

Active

The drag.

Coordinates

Where the drag is now, in viewport coordinates.

Coordinates

Unit vector of the pressed arrow key.

useDnDContext and provideDnDContext

function useDnDContext(): DnDContext;

The provider above this component, as an opaque handle for provideDnDContext: it bridges the provider into shadow roots and separate Vue apps.

function provideDnDContext(app: App, context: DnDContext): void;

Shares a provider (from useDnDContext()) with another Vue app, e.g. one mounted inside a shadow root.

// In a component under the provider:
const context = useDnDContext();
// A second Vue app (a widget mounted in a shadow root) joins the same drag:
const widget = createApp(Widget);
provideDnDContext(widget, context);

A provider as useDnDContext() returns it, to hand to provideDnDContext(): another Vue app (e.g. one mounted in a shadow root) then works with the same provider. Opaque: it has no public members, so it never changes with the library's internals.