Custom Overlay per Draggable
Each draggable element can specify its own preview component via the render option in makeDraggable. When that element is dragged, DragPreview renders your component instead of a clone of the original element.
Demo
Tasks show a card-style preview with priority badge. Notes show a tilted sticky-note preview. Each item type has its own visual identity while dragging.
Setup
Pass render: markRaw(MyOverlayComponent) when registering the draggable:
import { markRaw } from 'vue';
import { makeDraggable } from '@vue-dnd-kit/core';
import TaskOverlay from './TaskOverlay.vue';
makeDraggable(itemRef, {
render: markRaw(TaskOverlay),
data: () => ({ id: props.id, label: props.label, priority: props.priority }),
});markRaw
Wrap the component with markRaw() so Vue doesn't make it deeply reactive. This is required for components stored in reactive state.
Writing the preview component
The preview component is rendered inside DragPreview's already-positioned container, so you don't need to handle positioning yourself. Use useDnDProvider() to read the dragged item's data:
<script setup lang="ts">
import { computed } from 'vue';
import { useDnDProvider } from '@vue-dnd-kit/core';
const { entities } = useDnDProvider();
const draggingData = computed(() => {
const [node] = entities.draggingMap.keys();
if (!node) return null;
return entities.draggableMap.get(node)?.data?.() as {
label: string;
priority: string;
} | null;
});
</script>
<template>
<div class="task-preview">
<span>📋 {{ draggingData?.label }}</span>
<span class="priority">{{ draggingData?.priority }}</span>
</div>
</template>Global preview override
To replace the preview for the entire provider (not per-item), use the #preview slot on DnDProvider, or set provider.preview.render from any descendant:
<!-- Slot approach -->
<DnDProvider>
<template #preview>
<MyGlobalOverlay />
</template>
</DnDProvider>// Programmatic approach (from any child component)
import { markRaw, onMounted } from 'vue';
import { useDnDProvider } from '@vue-dnd-kit/core';
import MyGlobalOverlay from './MyGlobalOverlay.vue';
const provider = useDnDProvider();
onMounted(() => {
provider.preview.render.value = markRaw(MyGlobalOverlay);
});Without h() — plain component reference
The simplest way to specify a render component is a plain import:
import { markRaw } from 'vue';
import MyOverlay from './MyOverlay.vue';
makeDraggable(itemRef, {
render: markRaw(MyOverlay),
});Passing props via TSX
Since the render component receives no props (it reads state from useDnDProvider()), you normally don't need to pass props. If you do need a parametric component, use @vitejs/plugin-vue-jsx and define an inline component with TSX:
Install:
npm install -D @vitejs/plugin-vue-jsxvite.config.ts:
import vueJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
plugins: [vue(), vueJsx()],
});Usage — inline component with props via TSX:
// MyComponent.vue or a .tsx file
import { defineComponent, markRaw } from 'vue';
import MyOverlay from './MyOverlay.vue';
const color = 'blue';
makeDraggable(itemRef, {
// Wrap in defineComponent so it's a proper component, not a VNode
render: markRaw(
defineComponent(() => () => <MyOverlay color={color} />)
),
});This is the escape hatch when you need static props baked into the component. For dynamic data that changes per drag, the data option + useDnDProvider() inside the preview is the recommended pattern.
See also
- Changing Overlay — replacing the preview globally per drop zone
- DnDProvider —
#previewslot - makeDraggable —
renderanddataoptions