Docs/API
useDnD and monitors
The drag state and its events, anywhere under the provider — and the provider itself, handed to another Vue app.
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.
DnDSelectionThe selected draggables (see DnDSelection).
() => voidCancels 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) => booleanWhether id is selected.
(...ids: Key[]) => voidSelects these ids.
(...ids: Key[]) => voidDeselects these ids.
(id: Key, force?: boolean) => voidSelects id or deselects it; force: true selects it, false deselects it.
(ids: Iterable<Key>) => voidSelects these ids and no others.
() => voidDeselects 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) => voidA drag started, after its activation.
(event: DnDEvent) => voidThe drag moved (once per frame at most).
(event: DnDEvent) => voidThe 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) => voidA notification once the drop is done: sortable lists have run their onDrop and written it.
(event: DnDEvent) => voidThe drag was cancelled, or its drop refused: the items fly back.
(event: DnDEvent) => voidonBeforeDrop answered 'continue': there was no drop, and the drag goes on.
(event: DnDEvent | DnDEndEvent) => voidThe 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.
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.