vuedraggable vs vue-draggable-plus
Drag-and-Drop Lists in Vue 3 Applications
vuedraggablevue-draggable-plusSimilar Packages:

Drag-and-Drop Lists in Vue 3 Applications

Both vue-draggable-plus and vuedraggable are Vue components that wrap the SortableJS library to enable drag-and-drop functionality for lists. vuedraggable is the original and widely adopted wrapper, supporting both Vue 2 and Vue 3. vue-draggable-plus is a newer alternative built specifically for Vue 3, focusing on improved TypeScript support and alignment with modern Vue reactivity systems. Developers use these tools to create sortable lists, Kanban boards, and rearrangeable grids without managing low-level DOM events manually.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
vuedraggable1,445,21420,586-2866 years agoMIT
vue-draggable-plus280,2174,011215 kB1097 months agoMIT

Vue Drag-and-Drop Libraries: vue-draggable-plus vs vuedraggable

Both vue-draggable-plus and vuedraggable solve the same core problem β€” making lists sortable in Vue applications using SortableJS. However, they differ in how they handle Vue 3 reactivity, TypeScript definitions, and maintenance focus. Let's look at the technical differences that impact daily development.

πŸ“¦ Installation and Setup

vue-draggable-plus is installed as a modern Vue 3 package. It exports the component directly for use in <script setup>.

// vue-draggable-plus
import { VueDraggable } from 'vue-draggable-plus'

// Usage in template
// <VueDraggable v-model="items" ... />

vuedraggable requires ensuring you are on version 4+ for Vue 3 support. It uses a default export for the component.

// vuedraggable
import draggable from 'vuedraggable'

// Usage in template
// <draggable v-model="items" ... />

🎯 Component Structure and Slots

Both libraries use the v-model directive to sync the list state. However, how you render items inside the draggable area differs slightly in recommended patterns.

vue-draggable-plus encourages using the item slot for clarity and better Vue 3 integration.

<!-- vue-draggable-plus -->
<template>
  <VueDraggable v-model="tasks" item-key="id">
    <template #item="{ element }">
      <div>{{ element.title }}</div>
    </template>
  </VueDraggable>
</template>

vuedraggable supports the item slot in Vue 3 versions, but older patterns often used v-for directly on the component (which is discouraged in Vue 3).

<!-- vuedraggable -->
<template>
  <draggable v-model="tasks" item-key="id">
    <template #item="{ element }">
      <div>{{ element.title }}</div>
    </template>
  </draggable>
</template>

πŸ›‘οΈ TypeScript Support

Type safety is a major differentiator. Modern Vue projects rely heavily on TypeScript, and the quality of type definitions affects developer speed.

vue-draggable-plus ships with built-in TypeScript definitions. You get autocomplete for props like group, sort, and disabled without extra setup.

// vue-draggable-plus
// Types are inferred automatically
const handleEnd = (evt: any) => {
  console.log(evt.oldIndex, evt.newIndex)
}

vuedraggable has community-maintained types or requires manual declaration in some setups. You might need to augment types or cast props to avoid errors.

// vuedraggable
// May require manual type extension
interface DraggableProps {
  modelValue: any[]
  itemKey: string
}

⚑ Reactivity and Vue 3.6+ Compatibility

Vue 3.6+ introduced changes to how reactivity handles arrays and proxies. This broke some older wrappers.

vue-draggable-plus was built after these changes. It handles array mutations correctly without triggering excessive re-renders or losing state.

// vue-draggable-plus
// Handles splice/move events cleanly
const items = ref([{ id: 1 }, { id: 2 }])
// Dragging updates 'items' without warning logs

vuedraggable (early Vue 3 versions) had issues with reactivity warnings when moving items. While patched in later v4 releases, some edge cases still require workarounds.

// vuedraggable
// Older versions might log warnings on move
// Ensure using v4.1.0+ for best stability
const items = ref([{ id: 1 }, { id: 2 }])

πŸ“‘ Event Handling

Both libraries expose SortableJS events via Vue props. The naming convention is consistent, but type safety varies.

vue-draggable-plus provides typed event handlers. You know exactly what data evt contains.

<!-- vue-draggable-plus -->
<VueDraggable 
  v-model="list" 
  @end="onEnd"
/>

<script setup lang="ts">
const onEnd = (evt: any) => {
  // Fully typed event object
}
</script>

vuedraggable uses the same event names but may lack strict typing for the event payload in some IDEs.

<!-- vuedraggable -->
<draggable 
  v-model="list" 
  @end="onEnd"
/>

<script setup>
const onEnd = (evt) => {
  // evt structure depends on runtime
}
</script>

πŸ”§ Advanced Configuration: Groups

Connecting two lists together requires the group prop. Both handle this similarly, but syntax consistency matters.

vue-draggable-plus ensures the group object is reactive and typed.

<!-- vue-draggable-plus -->
<VueDraggable 
  v-model="listA" 
  :group="{ name: 'shared', pull: true, put: true }" 
/>

vuedraggable supports the same object structure. However, reactivity issues in older versions sometimes required wrapping the group object in toRef.

<!-- vuedraggable -->
<draggable 
  v-model="listA" 
  :group="{ name: 'shared', pull: true, put: true }" 
/>

🧩 Similarities: Shared Capabilities

Despite the differences, both libraries share the same underlying engine and core features.

1. Underlying Engine

  • Both use SortableJS for the heavy lifting.
  • Support touch devices, animations, and fallback classes.
// Both support standard SortableJS options
const options = {
  animation: 150,
  ghostClass: 'ghost'
}

2. Two-Way Binding

  • Both use v-model to sync the array.
  • Changes in the UI update the data, and data changes update the UI.
<!-- Both -->
<component v-model="myList" />

3. Custom Drag Handles

  • Both allow restricting drag to specific elements using handle.
<!-- Both -->
<component v-model="list" handle=".handle" />
<div class="handle">::</div>

4. Transition Support

  • Both work with Vue's <TransitionGroup> for move animations.
<!-- Both -->
<TransitionGroup name="list">
  <component v-model="list" />
</TransitionGroup>

5. Disabled State

  • Both support disabling drag via a boolean prop.
<!-- Both -->
<component v-model="list" :disabled="isReadOnly" />

πŸ“Š Summary: Key Differences

Featurevue-draggable-plusvuedraggable
Vue VersionVue 3 OnlyVue 2 & Vue 3
TypeScriptβœ… Built-in, Strong⚠️ Community/Manual
Reactivityβœ… Optimized for Vue 3.6+⚠️ Requires v4+
Maintenance🟒 Active, Vue 3 Focused🟑 Mature, Slower Updates
BundleπŸƒ LightweightπŸ“¦ Standard

πŸ’‘ The Big Picture

vue-draggable-plus is the modern choice for greenfield Vue 3 projects. It removes friction for TypeScript users and avoids known reactivity pitfalls. It feels like a native part of the Vue 3 ecosystem.

vuedraggable remains a solid option for teams maintaining Vue 2 apps or those who need a single solution for mixed Vue 2/3 repositories. It is stable but requires more care when configuring strict TypeScript environments.

Final Thought: If you are starting fresh with Vue 3 and TypeScript, vue-draggable-plus saves time on type definitions and debugging. If you are migrating an existing app using vuedraggable, staying on it is reasonable unless you hit specific reactivity bugs.

How to Choose: vuedraggable vs vue-draggable-plus

  • vuedraggable:

    Choose vuedraggable if you are maintaining a legacy Vue 2 codebase or migrating incrementally to Vue 3. It is also suitable for JavaScript-only projects where strict TypeScript definitions are not a priority. Its long history means many existing tutorials and examples are available for reference.

  • vue-draggable-plus:

    Choose vue-draggable-plus for new Vue 3 projects, especially those using TypeScript. It offers stronger type definitions and fixes specific reactivity issues found in older wrappers when used with Vue 3.6+. It is the better fit for teams prioritizing type safety and long-term maintenance in the modern Vue ecosystem.

README for vuedraggable

Vue.Draggable

CircleCI Coverage codebeat badge GitHub open issues npm download npm download per month npm version MIT License

Vue component (Vue.js 2.0) or directive (Vue.js 1.0) allowing drag-and-drop and synchronization with view model array.

Based on and offering all features of Sortable.js

Demo

demo gif

Live Demos

https://sortablejs.github.io/Vue.Draggable/

https://david-desmaisons.github.io/draggable-example/

Features

  • Full support of Sortable.js features:
    • Supports touch devices
    • Supports drag handles and selectable text
    • Smart auto-scrolling
    • Support drag and drop between different lists
    • No jQuery dependency
  • Keeps in sync HTML and view model list
  • Compatible with Vue.js 2.0 transition-group
  • Cancellation support
  • Events reporting any changes when full control is needed
  • Reuse existing UI library components (such as vuetify, element, or vue material etc...) and make them draggable using tag and componentData props

Backers

Admin Dashboard Templates made with Vue, React and Angular.

Donate

Find this project useful? You can buy me a :coffee: or a :beer:

paypal

Installation

With npm or yarn

yarn add vuedraggable

npm i -S vuedraggable

Beware it is vuedraggable for Vue 2.0 and not vue-draggable which is for version 1.0

with direct link


<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<!-- CDNJS :: Sortable (https://cdnjs.com/) -->
<script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
<!-- CDNJS :: Vue.Draggable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

cf example section

For Vue.js 2.0

Use draggable component:

Typical use:

<draggable v-model="myArray" group="people" @start="drag=true" @end="drag=false">
   <div v-for="element in myArray" :key="element.id">{{element.name}}</div>
</draggable>

.vue file:

  import draggable from 'vuedraggable'
  ...
  export default {
        components: {
            draggable,
        },
  ...

With transition-group:

<draggable v-model="myArray">
    <transition-group>
        <div v-for="element in myArray" :key="element.id">
            {{element.name}}
        </div>
    </transition-group>
</draggable>

Draggable component should directly wrap the draggable elements, or a transition-component containing the draggable elements.

With footer slot:

<draggable v-model="myArray" draggable=".item">
    <div v-for="element in myArray" :key="element.id" class="item">
        {{element.name}}
    </div>
    <button slot="footer" @click="addPeople">Add</button>
</draggable>

With header slot:

<draggable v-model="myArray" draggable=".item">
    <div v-for="element in myArray" :key="element.id" class="item">
        {{element.name}}
    </div>
    <button slot="header" @click="addPeople">Add</button>
</draggable>

With Vuex:

<draggable v-model='myList'>
computed: {
    myList: {
        get() {
            return this.$store.state.myList
        },
        set(value) {
            this.$store.commit('updateList', value)
        }
    }
}

Props

value

Type: Array
Required: false
Default: null

Input array to draggable component. Typically same array as referenced by inner element v-for directive.
This is the preferred way to use Vue.draggable as it is compatible with Vuex.
It should not be used directly but only though the v-model directive:

<draggable v-model="myArray">

list

Type: Array
Required: false
Default: null

Alternative to the value prop, list is an array to be synchronized with drag-and-drop.
The main difference is that list prop is updated by draggable component using splice method, whereas value is immutable.
Do not use in conjunction with value prop.

All sortable options

New in version 2.19

Sortable options can be set directly as vue.draggable props since version 2.19.

This means that all sortable option are valid sortable props with the notable exception of all the method starting by "on" as draggable component expose the same API via events.

kebab-case propery are supported: for example ghost-class props will be converted to ghostClass sortable option.

Example setting handle, sortable and a group option:

<draggable
        v-model="list"
        handle=".handle"
        :group="{ name: 'people', pull: 'clone', put: false }"
        ghost-class="ghost"
        :sort="false"
        @change="log"
      >
      <!-- -->
</draggable>

tag

Type: String
Default: 'div'

HTML node type of the element that draggable component create as outer element for the included slot.
It is also possible to pass the name of vue component as element. In this case, draggable attribute will be passed to the create component.
See also componentData if you need to set props or event to the created component.

clone

Type: Function
Required: false
Default: (original) => { return original;}

Function called on the source component to clone element when clone option is true. The unique argument is the viewModel element to be cloned and the returned value is its cloned version.
By default vue.draggable reuses the viewModel element, so you have to use this hook if you want to clone or deep clone it.

move

Type: Function
Required: false
Default: null

If not null this function will be called in a similar way as Sortable onMove callback. Returning false will cancel the drag operation.

function onMoveCallback(evt, originalEvent){
   ...
    // return false; β€” for cancel
}

evt object has same property as Sortable onMove event, and 3 additional properties:

  • draggedContext: context linked to dragged element
    • index: dragged element index
    • element: dragged element underlying view model element
    • futureIndex: potential index of the dragged element if the drop operation is accepted
  • relatedContext: context linked to current drag operation
    • index: target element index
    • element: target element view model element
    • list: target list
    • component: target VueComponent

HTML:

<draggable :list="list" :move="checkMove">

javascript:

checkMove: function(evt){
    return (evt.draggedContext.element.name!=='apple');
}

See complete example: Cancel.html, cancel.js

componentData

Type: Object
Required: false
Default: null

This props is used to pass additional information to child component declared by tag props.
Value:

  • props: props to be passed to the child component
  • attrs: attrs to be passed to the child component
  • on: events to be subscribe in the child component

Example (using element UI library):

<draggable tag="el-collapse" :list="list" :component-data="getComponentData()">
    <el-collapse-item v-for="e in list" :title="e.title" :name="e.name" :key="e.name">
        <div>{{e.description}}</div>
     </el-collapse-item>
</draggable>
methods: {
    handleChange() {
      console.log('changed');
    },
    inputChanged(value) {
      this.activeNames = value;
    },
    getComponentData() {
      return {
        on: {
          change: this.handleChange,
          input: this.inputChanged
        },
        attrs:{
          wrap: true
        },
        props: {
          value: this.activeNames
        }
      };
    }
  }

Events

  • Support for Sortable events:

    start, add, remove, update, end, choose, unchoose, sort, filter, clone
    Events are called whenever onStart, onAdd, onRemove, onUpdate, onEnd, onChoose, onUnchoose, onSort, onClone are fired by Sortable.js with the same argument.
    See here for reference

    Note that SortableJS OnMove callback is mapped with the move prop

HTML:

<draggable :list="list" @end="onEnd">
  • change event

    change event is triggered when list prop is not null and the corresponding array is altered due to drag-and-drop operation.
    This event is called with one argument containing one of the following properties:

    • added: contains information of an element added to the array
      • newIndex: the index of the added element
      • element: the added element
    • removed: contains information of an element removed from to the array
      • oldIndex: the index of the element before remove
      • element: the removed element
    • moved: contains information of an element moved within the array
      • newIndex: the current index of the moved element
      • oldIndex: the old index of the moved element
      • element: the moved element

Slots

Limitation: neither header or footer slot works in conjunction with transition-group.

Header

Use the header slot to add none-draggable element inside the vuedraggable component. Important: it should be used in conjunction with draggable option to tag draggable element. Note that header slot will always be added before the default slot regardless its position in the template. Ex:

<draggable v-model="myArray" draggable=".item">
    <div v-for="element in myArray" :key="element.id" class="item">
        {{element.name}}
    </div>
    <button slot="header" @click="addPeople">Add</button>
</draggable>

Footer

Use the footer slot to add none-draggable element inside the vuedraggable component. Important: it should be used in conjunction with draggable option to tag draggable elements. Note that footer slot will always be added after the default slot regardless its position in the template. Ex:

<draggable v-model="myArray" draggable=".item">
    <div v-for="element in myArray" :key="element.id" class="item">
        {{element.name}}
    </div>
    <button slot="footer" @click="addPeople">Add</button>
</draggable>

Gotchas

  • Vue.draggable children should always map the list or value prop using a v-for directive

    • You may use header and footer slot to by-pass this limitation.
  • Children elements inside v-for should be keyed as any element in Vue.js. Be carefull to provide revelant key values in particular:

    • typically providing array index as keys won't work as key should be linked to the items content
    • cloned elements should provide updated keys, it is doable using the clone props for example

Example

Full demo example

draggable-example

For Vue.js 1.0

See here