Drag to Reorder
useDraggableList() — pointer-event drag-to-reorder composable. Shows a fixed-position ghost element that follows the cursor, animates neighbouring items with translateY, and auto-scrolls the container when dragging near the edges.
function useDraggableList<T>(options: UseDraggableListOptions<T>): UseDraggableListReturnOptions
items
Ref<T[]>
Reactive items array.
onReorder
(newItems, from, to) => void
Called after a successful drop with the reordered array.
isDragDisabled
(item, index) => boolean
Return true to prevent an item from being dragged.
scrollContainer
HTMLElement | Ref<HTMLElement | null>
Container for auto-scroll when dragging near its edges.
Return value
dragIndex
Readonly<Ref<number>>
Index of the item being dragged, -1 when idle.
overIndex
Readonly<Ref<number>>
Index of the current drop target.
isDragging
Readonly<Ref<boolean>>
Whether a drag is in progress.
ghostStyle
Readonly<Ref<CSSProperties>>
Fixed-position styles for the ghost element.
getItemStyle
(index: number) => CSSProperties
Per-item styles: opacity:0 for the placeholder, translateY for animated neighbours.
getItemProps
(index: number) => DraggableItemProps
Props to spread on each draggable item (data-drag-index, onPointerdown, CSS classes). Dragging is entirely pointer-event based — this never sets the native HTML5 draggable attribute, so it doesn't trigger the browser's built-in drag-ghost/text-selection behavior.
CSS classes added by getItemProps
| Class | When |
|---|---|
vvsk-drag--dragging | Applied to the placeholder (the item being dragged) |
vvsk-drag--over | Applied to the current drop target |
vvsk-drag--disabled | Applied when isDragDisabled returns true |
Auto-scroll
When scrollContainer is provided, the list automatically scrolls up or down when the cursor enters a 60 px zone near the container edges. Scroll speed is proportional to the distance (max 14 px per frame).
Example
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggableList } from 'vue-virtual-scroller-kit'
interface Card {
id: number
label: string
}
const cards = ref<Card[]>(Array.from({ length: 50 }, (_, i) => ({ id: i, label: `Card ${i + 1}` })))
const listRef = ref<HTMLElement | null>(null)
const { isDragging, dragIndex, ghostStyle, getItemStyle, getItemProps } = useDraggableList({
items: cards,
scrollContainer: listRef,
onReorder: (newItems) => {
cards.value = newItems
},
})
</script>
<template>
<div
ref="listRef"
style="display: flex; flex-direction: column; gap: 6px; overflow-y: auto; height: 500px"
>
<div
v-for="(card, index) in cards"
:key="card.id"
v-bind="getItemProps(index)"
:style="getItemStyle(index)"
class="card"
>
⣿ {{ card.label }}
</div>
</div>
<Teleport to="body">
<div v-if="isDragging && dragIndex >= 0" class="card card--ghost" :style="ghostStyle">
⣿ {{ cards[dragIndex]?.label }}
</div>
</Teleport>
</template>
<style>
.card {
padding: 12px 16px;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
cursor: grab;
user-select: none;
}
.card--ghost {
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.3);
transform: scale(1.02);
pointer-events: none;
}
</style>