Docs/API
DnDProvider
The component that owns the drag — input, the overlay, the selection, auto-scroll and the live region — for everything rendered inside it.
const DnDProvider: Component<DnDProviderProps>;Owns the drags of everything rendered inside it: input, the overlay, the selection, auto-scroll and the screen reader announcements (see DnDProviderProps).
<script setup lang="ts">
import { DnDProvider } from '@vue-dnd-kit/core';
</script>
<template>
<DnDProvider :animation="{ duration: 200 }" @drag-end="save">
<RouterView />
<template #overlay="{ items, over, accepted }">
<!-- optional: a custom overlay for every drag -->
</template>
</DnDProvider>
</template>
Every composable must run in a component rendered inside a provider. One provider for the
whole app is the goal: parts of the page get their own rules with
makeDragArea. See the provider guide.
Most props are read when the provider needs them: change one, and the next drag uses it.
Loading the demo…
<script setup lang="ts">
import { computed, ref } from 'vue';
import { DnDProvider, type ActivationOptions } from '@vue-dnd-kit/core';
import PropRow from './PropRow.vue';
import DensityList from './DensityList.vue';
const ACTIVATIONS: Record<string, ActivationOptions | undefined> = {
default: undefined,
'{ mouse: { delay: 300 } }': { mouse: { delay: 300, tolerance: 5 } },
};
const overlayFit = ref<'slot' | 'source'>('slot');
const activation = ref('default');
const keyboard = ref<'true' | 'false'>('true');
const copyModifier = ref<'Alt' | 'Shift' | 'null'>('Alt');
// The provider reads its props when it needs them: a change applies to the next drag.
const props = computed(() => ({
overlayFit: overlayFit.value,
activation: ACTIVATIONS[activation.value],
// `false` turns keyboard drags off; an object sets their keys and steps.
keyboard: keyboard.value === 'true' ? undefined : (false as const),
copyModifier: copyModifier.value === 'null' ? null : copyModifier.value,
}));
const compact = ref([
{ id: 'copy', title: 'Write copy' },
{ id: 'login', title: 'Fix login' },
]);
const roomy = ref([
{ id: 'sprint', title: 'Plan the sprint' },
{ id: 'review', title: 'Review the PR' },
]);
</script>
<template>
<DnDProvider v-bind="props">
<div class="demo">
<div class="props" role="group" aria-label="DnDProvider props">
<PropRow v-model="overlayFit" name="overlay-fit" :options="['slot', 'source']" />
<PropRow v-model="activation" name=":activation" :options="Object.keys(ACTIVATIONS)" />
<PropRow v-model="keyboard" name=":keyboard" :options="['true', 'false']" />
<PropRow v-model="copyModifier" name="copy-modifier" :options="['Alt', 'Shift', 'null']" />
</div>
<div class="lists">
<DensityList v-model="compact" title="Compact" compact />
<DensityList v-model="roomy" title="Roomy" />
</div>
</div>
</DnDProvider>
</template>
<style scoped>
/* Room for every row in one list: the lists grow downward and nothing moves under the pointer. */
.demo {
display: flex;
flex-direction: column;
align-items: center;
gap: 18px;
min-height: 430px;
}
.props {
display: grid;
grid-template-columns: auto auto;
align-items: center;
gap: 6px 14px;
max-width: 100%;
padding: 12px 14px;
border: 1px solid color-mix(in srgb, currentColor 12%, transparent);
border-radius: 12px;
background: color-mix(in srgb, currentColor 3%, Canvas);
}
.lists {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
gap: 14px;
}
@media (max-width: 480px) {
.props {
grid-template-columns: minmax(0, 1fr);
}
}
</style>Where the overlay renders
overlayTo moves the overlay out of body: into a modal <dialog>, which sits above
everything else on the page. Turn the switch off, and the rows you drag vanish under the
dialog. The overlay guide explains when
it is needed.
Loading the demo…
<script setup lang="ts">
import { computed, ref, useTemplateRef } from 'vue';
import { DnDProvider } from '@vue-dnd-kit/core';
import ColumnList from './ColumnList.vue';
const columns = ref(['Name', 'Status', 'Owner', 'Due date', 'Priority']);
const dialog = useTemplateRef<HTMLDialogElement>('dialog');
const open = ref(false);
const intoDialog = ref(true);
// A modal <dialog> sits on the top layer, above everything in <body>, the overlay
// included. While it is open, the overlay renders inside it.
const overlayTo = computed(() => (open.value && intoDialog.value ? '#columns-dialog' : 'body'));
const show = () => {
dialog.value?.showModal();
open.value = true;
};
</script>
<template>
<DnDProvider :overlay-to="overlayTo">
<div class="sheet">
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column">{{ column }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in 2" :key="row">
<td v-for="column in columns" :key="column"><span class="skeleton" /></td>
</tr>
</tbody>
</table>
<button type="button" class="primary" @click="show">Edit columns</button>
</div>
<dialog id="columns-dialog" ref="dialog" @close="open = false">
<h3>Columns</h3>
<p>Drag to change their order.</p>
<ColumnList v-model:columns="columns" />
<footer>
<label class="switch">
<input v-model="intoDialog" type="checkbox" />
Overlay inside the dialog
</label>
<button type="button" class="primary" @click="dialog?.close()">Done</button>
</footer>
</dialog>
</DnDProvider>
</template>
<style scoped>
.sheet {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
width: 100%;
}
table {
width: min(100%, 520px);
border-collapse: collapse;
font-size: 12px;
}
th {
padding: 8px;
border-bottom: 1px solid color-mix(in srgb, currentColor 15%, transparent);
font-weight: 600;
text-align: left;
white-space: nowrap;
}
td {
padding: 10px 8px;
border-bottom: 1px solid color-mix(in srgb, currentColor 8%, transparent);
}
.skeleton {
display: block;
height: 8px;
border-radius: 4px;
background: color-mix(in srgb, currentColor 10%, transparent);
}
button {
padding: 7px 14px;
border: 0;
border-radius: 8px;
font: 600 12px/1.2 system-ui, sans-serif;
cursor: pointer;
}
.primary {
background: #42b883;
color: #04130c;
}
button:focus-visible {
outline: 2px solid #42b883;
outline-offset: 2px;
}
/* No transform on the dialog: it would move the overlay's fixed position. */
dialog {
width: min(340px, calc(100vw - 32px));
box-sizing: border-box;
margin: auto;
padding: 18px;
border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
border-radius: 16px;
background: Canvas;
color: CanvasText;
box-shadow: 0 30px 60px -20px rgb(0 0 0 / 0.6);
}
dialog::backdrop {
background: rgb(0 0 0 / 0.45);
}
h3 {
margin: 0;
font-size: 15px;
}
p {
margin: 4px 0 14px;
font-size: 12px;
opacity: 0.6;
}
footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
}
.switch {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.switch input {
accent-color: #42b883;
}
</style>Props
Props of DnDProvider. Most are read when the provider needs them: change one, and the next drag uses it.
CollisionDetectionHow the target under the drag is chosen. Default pointerFirst; another built-in (closestCorners) or your own.
Modifier[]Modifiers for every drag, applied first: those of drag areas, draggables and lists come after.
LayoutAnimationDefault FLIP animation for sortable lists.
DropAnimationHow the overlay lands on drop and flies back on cancel, when nothing closer sets one: the drop target's overDropAnimation comes first, then the dragged item's own (or its list's), then this one.
AutoAnimationDefault animateChanges of sortable lists and trees: changes of their items that no drag made. Off by default.
boolean | AutoScrollOptionsScrolls containers and the page when a drag nears their edges. Default true; options tune it, false turns it off. Lists, trees and drag areas can override it.
ActivationOptionsWhat a press must do before it becomes a drag, per pointer kind (see ActivationOptions for the defaults). Drag areas, lists, trees, items and draggables can override it.
KeyboardOptions | falseKeyboard drags: their keys and step. false turns them off; Escape still cancels any drag.
Sensor[]What turns input into drags. Default [pointerSensor, keyboardSensor]: leave one out, wrap or replace it, or add your own (defineSensor). The keyboard sensor also cancels any drag with Escape; keyboard: false turns its drags off but keeps that. The list may change at any time: sensors that leave it stop, cancelling their drag.
boolean | HapticsOptionsVibration for touch drags. Default: a short one when a drag starts after the long press; an object adds others (enter, drop, refuse); false turns them all off.
string | nullKey (KeyboardEvent.key) that turns a move into a copy. Default 'Alt'; null disables.
string | HTMLElementWhere the overlay renders: a selector or an element. Default 'body'. A modal <dialog> sits above the rest of the page: drags inside one need it there.
'slot' | 'source'Overlay size while dragging. 'slot' (default): it follows the slot the item will land in, so over another list it takes the size of that list's items, and the drop needs no resize. It resizes around the pointer, which stays at the same fraction of the box. 'source': it keeps the dragged element's size. The default overlay content re-lays out at the new size; it is never scaled.
number | 'all'Multi-drag: how many dragged items get a clone at pickup, the grabbed one and the others next to it in drop order. Default 5; 'all' clones every one that is rendered. The default overlay shows the clones and counts the rest (+N); in a layout of your own, DragPreview renders its slot for an item without one. Every clone flies: back to its item on a cancel, to its slot on a drop, into the zone a drop goes into, one after another. The clones are taken together, one layout per snapshot pass for all, but each costs reading its styles: mind long items on slow phones.
boolean | TransitionPropsHow the overlay changes from one component to another during a drag (a drop target's overOverlay takes over, an overlay ref is switched): true (default) crossfades them in 150 ms, false swaps them at once, and an object is the props of a Vue <Transition> around the content's root (name, mode, JS hooks for GSAP or motion). Without mode: 'out-in' both show at once: give the leaving one position: absolute (.<name>-leave-active) so they stack in the box.
string | Record<string, boolean> | Array<string | Record<string, boolean>>Classes for the overlay's content ([data-dnd-overlay-content]), the element that fills the overlay box: the one to animate as a whole while the library moves and sizes the box. A transition on it runs from @starting-style as the drag picks up, and back as it lands ([data-dnd-overlay='dropping']): the drop waits for it. Plain CSS reaches it outside <style scoped> (or with :global(...)), Tailwind with starting: and in-data-[dnd-overlay=dragging]:.
Partial<Announcements> | falseOwn texts for some events (see Announcements); false keeps the live region silent.
stringHow to drag with the keyboard, read out by screen readers for every draggable (its aria-describedby). Default: English instructions for Space, Enter, the arrows and Escape.
booleanInject the default looks: zero-specificity, layered CSS that dims placeholders and the items a multi-drag leaves in place (opacity: 0.4). Default true; false leaves the looks to you. What drags need is injected either way: no text selection and no scroll anchoring while dragging (virtual lists rely on it), the drag cursor (--dnd-cursor), and no link callout on a long press on iOS.
Events
| Event | Payload | When |
|---|---|---|
drag-start | DnDEvent | A drag started (after its activation). |
drag-move | DnDEvent | The pointer moved during a drag (once per frame at most). |
drag-over | DnDEvent | The target under the drag changed. |
drag-end | DnDEndEvent | A drop went through and was written. |
drag-cancel | DnDEvent | The drag was cancelled or the drop refused. |
drag-resume | DnDEvent | onBeforeDrop answered 'continue': there was no drop, and the drag goes on. |
drag-settle | DnDEndEvent or DnDEvent | The drag's overlay has landed: what drag-end or drag-cancel got. |
The same events, plus onBeforeDrop, are available anywhere under the
provider with useDnDMonitor. Their payloads are on
the events page.
Slot #overlay
Renders what follows the pointer, for every drag that has no overlay component of its own (see the overlay guide). Rendering nothing falls back to the clone.
What the overlay shows: the props of the provider's #overlay slot and of overlay components (overlay option of draggables, sortable items, lists and trees).
ActiveThe drag.
DragItem[]Every dragged item, the primary one first: one overlay for the whole multi-drag.
Over | nullThe drop target under the drag. While a decided drop animates, the target it lands on (as it was at the drop); null while a cancelled drag flies back.
booleanWhether over accepts the dragged items; false when over nothing.
DnDStatus'dragging', then 'dropping' while the overlay lands (or flies back).
DragModeWhat a drop would do now (see DnDEvent.mode): e.g. show a plus while it copies.
DropDestination | nullWhere a drop would put the dragged items now, in a sortable list or tree: e.g. tint the overlay by the folder a tree drag would go into. Outside every target it is the slot's place, where dropOutside: 'placeholder' (the default) keeps them. null when the drop would put them in no list: over a plain drop target, over a list that refuses them, outside with dropOutside: 'cancel' or 'remove'. While a decided drop animates, the place it lands on.
Keyboard
Keyboard drags (the provider's keyboard): their keys, and the step of free drags.
{ start?: string[]; drop?: string[]; cancel?: string[] }The keys that pick up (start), drop and cancel, as KeyboardEvent.code ('Space', 'KeyW') or .key ('Enter', 'w'). Default: Space and Enter pick up and drop, Escape cancels. The arrows always move.
numberPixels per arrow press for free (non-sortable) drags. Default 25.
Auto-scroll
How containers scroll when a drag nears their edges (the provider's autoScroll).
numberEdge zone as a fraction of the container size. Default 0.2 (clamped to 24–96px).
numberMaximum speed in px per 16ms frame.
numberSpeed in px per frame with the pointer at the container's edge: slower inside the edge zone, faster past the edge, up to maxSpeed. Default 10.
Haptics
When touch drags vibrate. Only drags started by touch do; where the browser can't vibrate (iOS Safari) or blocks it (no tap on the page yet), nothing happens.
HapticPatternThe drag starts, after the long press. Default true.
HapticPatternThe drag enters a drop target (a list, a zone) that accepts it. Default false.
HapticPatternThe drop goes through. Default false.
HapticPatternThe drop is refused (by a target or onBeforeDrop) and the items fly back. Default false.
A vibration (navigator.vibrate): its length in ms or an on/off pattern. true is the default one (10ms; [20, 40, 20] for refuse), false or 0 none.
type HapticPattern = boolean | number | number[]Announcements
Texts for screen readers, per event. Return undefined for the built-in text, '' for none. context has the positions the built-in texts of sortable lists speak.
A text is spoken by the provider's live region (moves once the drag rests for 500 ms); undefined leaves the event to the next source (the texts of integrations such as sortable positions, then the defaults). A move isn't announced for the target found at pickup: nothing moved yet.
(event: DnDEvent, context: AnnouncementContext) => string | undefinedA drag started.
(event: DnDEvent, context: AnnouncementContext) => string | undefinedThe drag moved over another target, or to another place in a list (once it rests for 500 ms).
(event: DnDEndEvent, context: AnnouncementContext) => string | undefinedThe drop went through, or the drag ended outside every target.
(event: DnDEvent, context: AnnouncementContext) => string | undefinedThe drag was cancelled, or its drop refused.
(event: DnDEvent, context: AnnouncementContext) => string | undefinedonBeforeDrop answered 'continue': the drag goes on.
What an announcement is about, beyond the event: for texts of your own (another language, other words) that say what the built-in ones say.
AnnouncedPositionIn a sortable list or tree: where the dragged items are. At the start, where they were picked up; while moving, where they would land (none outside the lists); at the end, where they landed, or where they went back to.
{ key: Key; label?: string; position: AnnouncedPosition }A swap: the item exchanged with the dragged one, and where it is now.
numberHow many items the drag carries.
booleanThe drag copies (group.pull: 'clone', the copy modifier).