vue-draggable-next vs vuedraggable
Drag-and-Drop List Management in Vue 3
vue-draggable-nextvuedraggableSimilar Packages:

Drag-and-Drop List Management in Vue 3

Both vue-draggable-next and vuedraggable are Vue components that wrap the Sortable.js library to enable drag-and-drop functionality in lists. They allow developers to reorder elements visually while keeping the underlying data array in sync. vuedraggable is the official wrapper maintained by the SortableJS organization, supporting both Vue 2 and Vue 3. vue-draggable-next is a community-driven fork created specifically to address Vue 3 compatibility during the transition period when the official package was still stabilizing its Vue 3 support.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
vue-draggable-next0558623 kB188 months agoMIT
vuedraggable020,615-2876 years agoMIT

vue-draggable-next vs vuedraggable: Architecture and Maintenance Compared

Both vue-draggable-next and vuedraggable solve the same problem — enabling drag-and-drop lists in Vue applications by wrapping Sortable.js. While their APIs look nearly identical, the difference lies in maintenance, official backing, and long-term reliability. Let's compare how they fit into a Vue 3 architecture.

📦 Installation and Setup

vuedraggable is the official package. It is installed directly from the main namespace.

npm install vuedraggable@next
// vuedraggable: Importing the component
import draggable from 'vuedraggable';

export default {
  components: { draggable }
};

vue-draggable-next is a community fork. It requires a different package name.

npm install vue-draggable-next
// vue-draggable-next: Importing the component
import draggable from 'vue-draggable-next';

export default {
  components: { draggable }
};

🔄 Vue 3 Reactivity: v-model Binding

Both packages support Vue 3's v-model to sync the list order with your data array. However, vuedraggable has broader documentation covering this pattern.

vuedraggable uses standard Vue 3 v-model binding.

<!-- vuedraggable: Two-way binding -->
<draggable v-model="taskList" item-key="id">
  <template #item="{ element }">
    <div>{{ element.title }}</div>
  </template>
</draggable>

vue-draggable-next implements the same v-model interface.

<!-- vue-draggable-next: Two-way binding -->
<draggable v-model="taskList" item-key="id">
  <template #item="{ element }">
    <div>{{ element.title }}</div>
  </template>
</draggable>

🎯 Event Handling: Start, End, and Change

Sortable.js emits events like start, end, and update. Both wrappers expose these as Vue events. You should verify event names match your version.

vuedraggable passes Sortable.js events directly to Vue listeners.

<!-- vuedraggable: Listening to events -->
<draggable 
  v-model="list" 
  @start="drag = true" 
  @end="drag = false"
  item-key="id"
>
  <!-- template content -->
</draggable>

vue-draggable-next supports the same event structure.

<!-- vue-draggable-next: Listening to events -->
<draggable 
  v-model="list" 
  @start="drag = true" 
  @end="drag = false"
  item-key="id"
>
  <!-- template content -->
</draggable>

🎬 Animation and Transitions

Vue's <transition-group> works alongside these components to animate list changes. Both support this, but configuration can vary slightly depending on the Vue version.

vuedraggable integrates cleanly with Vue 3 transitions.

<!-- vuedraggable: With transitions -->
<draggable v-model="list" item-key="id" 
  :component-data="{ tag: 'transition-group', name: 'list' }">
  <template #item="{ element }">
    <div class="item">{{ element.name }}</div>
  </template>
</draggable>

vue-draggable-next offers similar transition props.

<!-- vue-draggable-next: With transitions -->
<draggable v-model="list" item-key="id" 
  :component-data="{ tag: 'transition-group', name: 'list' }">
  <template #item="{ element }">
    <div class="item">{{ element.name }}</div>
  </template>
</draggable>

🛠️ Maintenance and Ecosystem Trust

This is the most critical difference for architectural decisions. One is official, and one is a community effort.

vuedraggable is maintained by the SortableJS organization.

  • Updates to Sortable.js are reflected quickly.
  • Issues are triaged by the core library maintainers.
  • Safe for long-term enterprise use.
// vuedraggable: Official repository
// github.com/SortableJS/Vue.Draggable

vue-draggable-next is maintained by individual contributors.

  • May lag behind Sortable.js updates.
  • Risk of abandonment if maintainers move on.
  • Useful as a stopgap or for specific legacy fixes.
// vue-draggable-next: Community repository
// github.com/hellodongsheng/vue-draggable-next

🤝 Similarities: Shared Ground Between Packages

Since both wrap the same underlying engine, they share most core features. Here are key overlaps:

1. 🧩 Same Underlying Engine

  • Both use Sortable.js for the heavy lifting.
  • Drag physics, touch support, and swap logic are identical.
// Both rely on Sortable.js core
// Configuration options like 'group', 'animation', 'ghostClass' work the same

2. 📋 List Synchronization

  • Both update the source array automatically.
  • No manual splice or move logic needed in most cases.
// Both: v-model syncs the array
// list.value moves automatically after drop

3. 🎨 Slot-Based Rendering

  • Both use Vue 3 slots to define item content.
  • Keeps logic separate from markup.
<!-- Both use the #item slot -->
<template #item="{ element }">
  <div>{{ element.name }}</div>
</template>

4. ⚙️ Configuration Props

  • Both accept Sortable.js options as props.
  • Examples: group, animation, disabled.
<!-- Both: Passing Sortable options -->
<draggable :animation="200" :group="'people'" v-model="list" item-key="id" />

5. 🌐 Vue 3 Compatibility

  • Both support Composition API and Options API.
  • Both work with TypeScript definitions.
// Both: TypeScript support
import draggable from 'vuedraggable'; // or 'vue-draggable-next'
// Types available for props and events

📊 Summary: Key Similarities

FeatureShared by Both
Core Engine🧩 Sortable.js
Data Sync📋 v-model array update
Rendering🎨 Vue 3 slots
Configuration⚙️ Sortable.js props
Vue Version🌐 Vue 3 supported

🆚 Summary: Key Differences

Featurevuedraggablevue-draggable-next
Maintainer🏢 SortableJS Org👤 Community Contributor
Longevity✅ High (Official)⚠️ Medium (Fork)
Update Speed🚀 Fast (Synced with core)🐢 Variable (Depends on author)
Recommendation🟢 Default for new projects🟡 Legacy or specific fixes
Vue 2 Support✅ Yes (v2 package)❌ Vue 3 only

💡 The Big Picture

vuedraggable is like the standard library 📚 — it's the official tool backed by the creators of the underlying engine. Ideal for new projects, enterprise systems, and teams that need guaranteed support.

vue-draggable-next is like a specialized patch 🔧 — created to fill a gap when the official tool wasn't ready for Vue 3. Useful for maintaining older Vue 3 projects that adopted it early, but less ideal for starting fresh.

Final Thought: Since vuedraggable now fully supports Vue 3, there is rarely a technical reason to choose the fork for new work. Stick with the official package to reduce risk and ensure compatibility with future Sortable.js updates.

How to Choose: vue-draggable-next vs vuedraggable

  • vue-draggable-next:

    Choose vue-draggable-next only if you are maintaining an existing codebase that already depends on it or if you encounter a specific bug in the official package that this fork has resolved. For greenfield projects, it is generally better to avoid community forks when an official, fully-compatible alternative exists.

  • vuedraggable:

    Choose vuedraggable for new projects because it is the official package maintained by the SortableJS team. It guarantees long-term support, timely updates for Sortable.js features, and consistent behavior across Vue 2 and Vue 3. It is the safest bet for enterprise applications where stability and maintenance matter most.

README for vue-draggable-next

vue-draggable-next

npm version Vue 3 TypeScript

🎯 Vue 3 drag-and-drop component based on Sortable.js

Features:

  • 🚀 Vue 3 Composition API support
  • 📱 Touch-friendly (mobile support)
  • 🎨 No CSS framework dependency
  • 📦 TypeScript definitions included
  • ⚡ Lightweight (~7kb gzipped)
  • 🔧 All Sortable.js options supported

📚 Live Demo & Playground | 📖 Migration Guide | 🎯 Examples

📦 Installation

# npm
npm install vue-draggable-next

# yarn  
yarn add vue-draggable-next

# pnpm
pnpm add vue-draggable-next

🚀 Quick Start

Basic Example (Composition API)

<template>
  <div class="drag-container">
    <draggable 
      v-model="list" 
      group="people" 
      @change="onListChange"
      item-key="id"
    >
      <template #item="{ element }">
        <div class="drag-item">
          {{ element.name }}
        </div>
      </template>
    </draggable>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { VueDraggableNext } from 'vue-draggable-next'

// Define the item type
interface Person {
  id: number
  name: string
}

// Reactive list
const list = ref<Person[]>([
  { id: 1, name: 'John' },
  { id: 2, name: 'Jane' },
  { id: 3, name: 'Bob' }
])

// Handle changes
const onListChange = (event: any) => {
  console.log('List changed:', event)
}
</script>

<style scoped>
.drag-container {
  min-height: 200px;
  padding: 20px;
}

.drag-item {
  padding: 10px;
  margin: 5px 0;
  background: #f0f0f0;
  border-radius: 4px;
  cursor: move;
  transition: background 0.2s;
}

.drag-item:hover {
  background: #e0e0e0;
}
</style>

Options API Example

<template>
  <draggable 
    :list="list" 
    class="drag-area"
    @change="handleChange"
  >
    <div 
      v-for="element in list"
      :key="element.id"
      class="drag-item"
    >
      {{ element.name }}
    </div>
  </draggable>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { VueDraggableNext } from 'vue-draggable-next'

export default defineComponent({
  components: {
    draggable: VueDraggableNext
  },
  data() {
    return {
      list: [
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' }
      ]
    }
  },
  methods: {
    handleChange(event: any) {
      console.log('Changed:', event)
    }
  }
})
</script>

📖 API Reference

Props

PropTypeDefaultDescription
modelValueArray[]Array to be synchronized with drag-and-drop (use with v-model)
listArray[]Alternative to modelValue, directly mutates the array
itemKeyString|FunctionundefinedKey to use for tracking items (recommended for better performance)
tagString'div'HTML tag for the root element
componentStringnullVue component name to use as root element
componentDataObjectnullProps/attrs to pass to the component
cloneFunction(item) => itemFunction to clone items when dragging
moveFunctionnullFunction to control move operations
groupString|ObjectundefinedSortable group options
sortBooleantrueEnable sorting within the list
disabledBooleanfalseDisable drag and drop
animationNumber0Animation speed (ms)
ghostClassString''CSS class for the ghost element
chosenClassString''CSS class for the chosen element
dragClassString''CSS class for the dragging element

Events

EventDescriptionPayload
@changeFired when the list changes{ added?, removed?, moved? }
@startDragging startedSortableEvent
@endDragging endedSortableEvent
@addItem added from another listSortableEvent
@removeItem removed to another listSortableEvent
@updateItem order changedSortableEvent
@sortAny change to the listSortableEvent
@chooseItem is chosenSortableEvent
@unchooseItem is unchosenSortableEvent

🎯 Examples

1. Between Multiple Lists

<template>
  <div class="lists-container">
    <div class="list-column">
      <h3>Todo</h3>
      <draggable 
        v-model="todoList"
        group="tasks"
        class="drag-area"
        :animation="150"
      >
        <div 
          v-for="item in todoList"
          :key="item.id"
          class="task-item"
        >
          {{ item.text }}
        </div>
      </draggable>
    </div>
    
    <div class="list-column">
      <h3>Done</h3>
      <draggable 
        v-model="doneList"
        group="tasks"
        class="drag-area"
        :animation="150"
      >
        <div 
          v-for="item in doneList"
          :key="item.id"
          class="task-item done"
        >
          {{ item.text }}
        </div>
      </draggable>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'

const todoList = ref([
  { id: 1, text: 'Learn Vue 3' },
  { id: 2, text: 'Build awesome apps' }
])

const doneList = ref([
  { id: 3, text: 'Read documentation' }
])
</script>

2. With Custom Handle

<template>
  <draggable 
    v-model="list"
    handle=".drag-handle"
    :animation="200"
  >
    <div 
      v-for="item in list"
      :key="item.id"
      class="item-with-handle"
    >
      <span class="drag-handle">⋮⋮</span>
      <span class="item-content">{{ item.name }}</span>
      <button @click="deleteItem(item.id)">Delete</button>
    </div>
  </draggable>
</template>

<script setup>
import { ref } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'

const list = ref([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' }
])

const deleteItem = (id) => {
  const index = list.value.findIndex(item => item.id === id)
  if (index > -1) {
    list.value.splice(index, 1)
  }
}
</script>

<style scoped>
.item-with-handle {
  display: flex;
  align-items: center;
  padding: 10px;
  margin: 5px 0;
  background: white;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.drag-handle {
  cursor: grab;
  margin-right: 10px;
  color: #999;
  user-select: none;
}

.drag-handle:active {
  cursor: grabbing;
}

.item-content {
  flex: 1;
}
</style>

3. With Transitions

<template>
  <draggable 
    v-model="list"
    tag="transition-group"
    :component-data="{
      tag: 'div',
      type: 'transition',
      name: 'fade'
    }"
    :animation="200"
  >
    <div 
      v-for="item in list"
      :key="item.id"
      class="fade-item"
    >
      {{ item.text }}
    </div>
  </draggable>
</template>

<script setup>
import { ref } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'

const list = ref([
  { id: 1, text: 'Smooth transition' },
  { id: 2, text: 'On drag and drop' }
])
</script>

<style scoped>
.fade-item {
  padding: 15px;
  margin: 8px 0;
  background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-radius: 8px;
  transition: all 0.3s ease;
}

.fade-enter-active, .fade-leave-active {
  transition: all 0.3s ease;
}

.fade-enter-from, .fade-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>

4. TypeScript Usage

// types.ts
export interface DraggableItem {
  id: string | number
  [key: string]: any
}

export interface DragChangeEvent<T = DraggableItem> {
  added?: {
    newIndex: number
    element: T
  }
  removed?: {
    oldIndex: number
    element: T
  }
  moved?: {
    newIndex: number
    oldIndex: number
    element: T
  }
}
<template>
  <draggable 
    v-model="items"
    @change="onListChange"
    item-key="id"
  >
    <template #item="{ element }: { element: TodoItem }">
      <div class="todo-item">
        <input 
          v-model="element.completed"
          type="checkbox"
        >
        <span :class="{ done: element.completed }">
          {{ element.text }}
        </span>
      </div>
    </template>
  </draggable>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'
import type { DragChangeEvent } from './types'

interface TodoItem {
  id: number
  text: string
  completed: boolean
}

const items = ref<TodoItem[]>([
  { id: 1, text: 'Learn TypeScript', completed: false },
  { id: 2, text: 'Build Vue 3 app', completed: true }
])

const onListChange = (event: DragChangeEvent<TodoItem>) => {
  if (event.added) {
    console.log('Added item:', event.added.element)
  }
  if (event.removed) {
    console.log('Removed item:', event.removed.element)
  }
  if (event.moved) {
    console.log('Moved item:', event.moved.element)
  }
}
</script>

🔧 Advanced Usage

Custom Clone Function

<script setup>
import { ref } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'

const sourceList = ref([
  { id: 1, name: 'Template Item', color: 'blue' }
])

const targetList = ref([])

// Deep clone function for complex objects
const cloneItem = (original) => {
  return {
    ...original,
    id: Date.now(), // Generate new ID
    name: `Copy of ${original.name}`
  }
}
</script>

<template>
  <div class="clone-demo">
    <div class="source">
      <h3>Source (Clone)</h3>
      <draggable 
        v-model="sourceList"
        :group="{ name: 'shared', pull: 'clone', put: false }"
        :clone="cloneItem"
        :sort="false"
      >
        <div v-for="item in sourceList" :key="item.id">
          {{ item.name }}
        </div>
      </draggable>
    </div>
    
    <div class="target">
      <h3>Target</h3>
      <draggable 
        v-model="targetList"
        group="shared"
      >
        <div v-for="item in targetList" :key="item.id">
          {{ item.name }}
        </div>
      </draggable>
    </div>
  </div>
</template>

Conditional Move

<script setup>
import { ref } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'

const list = ref([
  { id: 1, name: 'Movable item', locked: false },
  { id: 2, name: 'Locked item', locked: true },
  { id: 3, name: 'Another movable', locked: false }
])

// Prevent moving locked items
const checkMove = (event) => {
  // Don't allow moving locked items
  if (event.draggedContext.element.locked) {
    return false
  }
  
  // Don't allow dropping on locked items
  if (event.relatedContext.element?.locked) {
    return false
  }
  
  return true
}
</script>

<template>
  <draggable 
    v-model="list"
    :move="checkMove"
  >
    <div 
      v-for="item in list"
      :key="item.id"
      :class="{ locked: item.locked }"
      class="move-item"
    >
      {{ item.name }}
      <span v-if="item.locked">🔒</span>
    </div>
  </draggable>
</template>

<style scoped>
.move-item.locked {
  opacity: 0.6;
  cursor: not-allowed;
}
</style>

🔄 Migration from Vue 2

If you're migrating from the Vue 2 version, here are the key changes:

Before (Vue 2)

<draggable v-model="list" @end="onEnd">
  <div v-for="item in list" :key="item.id">
    {{ item.name }}
  </div>
</draggable>

After (Vue 3)

<!-- Option 1: Using item-key prop (recommended) -->
<draggable v-model="list" item-key="id" @end="onEnd">
  <template #item="{ element }">
    <div>{{ element.name }}</div>
  </template>
</draggable>

<!-- Option 2: Traditional approach (still works) -->
<draggable v-model="list" @end="onEnd">
  <div v-for="item in list" :key="item.id">
    {{ item.name }}
  </div>
</draggable>

Breaking Changes

  • Vue 3 required: This package only works with Vue 3
  • Composition API: Full support for <script setup> syntax
  • TypeScript: Built-in TypeScript definitions
  • Performance: Better performance with item-key prop

🎨 Styling & Customization

Ghost Element Styling

.ghost {
  opacity: 0.5;
  background: #c8ebfb;
  border: 2px dashed #2196f3;
}

.chosen {
  transform: rotate(5deg);
}

.drag {
  transform: rotate(0deg);
}

Smooth Animations

<draggable 
  v-model="list"
  :animation="300"
  easing="cubic-bezier(0.4, 0, 0.2, 1)"
  ghost-class="ghost"
  chosen-class="chosen"
  drag-class="drag"
>
  <!-- items -->
</draggable>

🔍 Troubleshooting

Common Issues

  1. Items not dragging: Check if disabled prop is false and items have unique keys
  2. Performance issues: Use item-key prop for better tracking
  3. Touch not working: Ensure touch-action CSS is not preventing touch events
  4. Transitions glitching: Use tag="transition-group" with proper transition classes

Debug Mode

<draggable 
  v-model="list"
  @start="console.log('Drag started', $event)"
  @end="console.log('Drag ended', $event)"
  @change="console.log('List changed', $event)"
>
  <!-- items -->
</draggable>

📱 Mobile Support

The component works out of the box on mobile devices. For better mobile experience:

.drag-item {
  /* Prevent text selection during drag */
  user-select: none;
  -webkit-user-select: none;
  
  /* Better touch targets */
  min-height: 44px;
  
  /* Smooth feedback */
  transition: transform 0.2s ease;
}

.drag-item:active {
  transform: scale(1.02);
}

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
git clone https://github.com/anish2690/vue-draggable-next.git

# Install dependencies
npm install

# Run development server
npm run playground:dev

# Run tests
npm test

# Build for production
npm run build

📄 License

MIT License

🌟 Credits

This project is heavily inspired by SortableJS/Vue.Draggable and built on top of SortableJS.

🙏 Support

If this project helps you, please consider:

  • ⭐ Starring the repository
  • 🐛 Reporting bugs
  • 💡 Suggesting features
  • 🤝 Contributing code

Made with ❤️ for the Vue.js community