Vue Draggable for Vue 2.x (Sortable.js Wrapper)
vuedraggable is a Vue 2.x component that provides drag-and-drop functionality for lists, leveraging the robust capabilities of Sortable.js. Version 2.24.3, published over five years ago, is specifically tailored for Vue 2 applications, offering declarative list reordering, multi-drag, and nested list support. It acts as a lightweight wrapper around Sortable.js, abstracting away direct DOM manipulation and integrating seamlessly with Vue's reactivity system. While newer versions (v4.x) exist for Vue 3, this entry focuses on the 2.x branch, which received consistent maintenance and updates for features like TypeScript definitions (since 2.24.0) and SSR compatibility fixes (like in 2.23.2). Its key differentiator remains the powerful and flexible drag-and-drop mechanics inherited from Sortable.js, presented in a Vue-idiomatic way, making it a widely adopted solution for interactive list management in legacy Vue 2 projects.
Common errors
-
[Vue warn]: Unknown custom element: <draggable> - did you register the component correctly?
cause The `draggable` component was imported but not properly registered with Vue.fixEnsure `draggable` is included in the `components` option of your Vue component (e.g., `components: { draggable }`) or registered globally with `Vue.component('draggable', draggable);`. -
TypeError: Cannot read property 'map' of undefined (or similar errors related to `v-model` array manipulation)
cause The `v-model` or `list` prop passed to `vuedraggable` is not an array, or it's an immutable value that cannot be modified directly.fixEnsure the data bound to `v-model` (or `list`) is a reactive array. If using Vuex, you might need to use the `list` prop and handle changes via `splice` in actions/mutations, or use the `clone` prop for complex objects to avoid direct mutation issues. -
Elements visually duplicate or disappear temporarily during drag, but data model is correct after refresh.
cause Often related to incorrect key binding (`:key`) for elements in the `v-for` loop within `vuedraggable`, leading Vue to mismanage element rendering during DOM updates.fixVerify that each item in your `v-for` loop inside `<draggable>` has a unique and stable `:key` prop, preferably an `id` from your data, not an array index. Also, ensure you are not accidentally creating new list instances instead of modifying the existing one. -
Drag-and-drop functionality does not work when `vuedraggable` is nested or within complex layouts (e.g., data tables).
cause Structural conflicts with parent components (like `<table>` or `v-data-table` in Vuetify) or incorrect placement of `draggable` and its child elements, potentially breaking HTML table structure.fixConsult `vuedraggable` examples for nesting. When using with tables, `vuedraggable` might need to wrap `<tbody>` or individual `<tr>` elements, often requiring `tag="tbody"` or `tag="tr"` on the draggable component. For `v-data-table`, ensure `draggable` is correctly placed within its slots, like `tbody`, and adjust the `tag` prop accordingly.
Warnings
- breaking Version 2.x of `vuedraggable` is designed exclusively for Vue 2.x applications. Attempting to use it directly in a Vue 3 project will result in runtime errors due to fundamental API changes in Vue 3. For Vue 3, `vuedraggable@next` or `vue-draggable-next` (v4.x) is required.
- gotcha Children elements within `vuedraggable`'s `v-for` loop must always have unique `:key` bindings. Using array indices (`:key="index"`) is explicitly discouraged, especially when items can be added, removed, or reordered, as it can lead to rendering issues, incorrect state updates, or duplicate elements. Keys should be stable and linked to the item's content.
- gotcha When working with TypeScript, while `vuedraggable` v2.24.0+ ships with basic component definitions, specific event types (like those emitted by Sortable.js) may require installing `@types/sortablejs` to get full type safety for event payloads, such as `SortableEvent`.
- deprecated Older versions of `vuedraggable` (pre-2.19.0) required passing Sortable.js options via a `:options` prop (e.g., `:options="{ group: 'name' }"`). Since v2.19.0, most Sortable.js options can be passed directly as `vuedraggable` props using kebab-case.
- gotcha If drag-and-drop functionality appears to be non-responsive, especially with input fields or text selection, it might be due to a conflict with default browser drag behavior or a lack of a defined drag handle.
Install
-
npm install vuedraggable -
yarn add vuedraggable -
pnpm add vuedraggable
Imports
- draggable
import { draggable } from 'vuedraggable'; // Not a named export usually const draggable = require('vuedraggable'); // CommonJS for browser/old Node setupsimport draggable from 'vuedraggable';
- Draggable Component Registration
// In a Vue component: export default { components: { draggable, }, // ... } - SortableEvent (for types)
import { SortableEvent } from 'vuedraggable'; // Types often reside in 'sortablejs' or global.import type { SortableEvent } from 'sortablejs';
Quickstart
import Vue from 'vue';
import draggable from 'vuedraggable';
new Vue({
el: '#app',
components: {
draggable,
},
data() {
return {
list1: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
],
list2: [
{ id: 4, name: 'Item 4' },
{ id: 5, name: 'Item 5' },
],
drag: false,
};
},
methods: {
log(evt) {
console.log('Drag event:', evt);
},
},
template: `
<div id="app">
<h2>List 1</h2>
<draggable
class="list-group"
tag="ul"
v-model="list1"
group="items"
@start="drag = true"
@end="drag = false"
@change="log"
>
<li
class="list-group-item"
v-for="element in list1"
:key="element.id"
>
{{ element.name }}
</li>
</draggable>
<h2>List 2</h2>
<draggable
class="list-group"
tag="ul"
v-model="list2"
group="items"
@start="drag = true"
@end="drag = false"
@change="log"
>
<li
class="list-group-item"
v-for="element in list2"
:key="element.id"
>
{{ element.name }}
</li>
</draggable>
<p>Dragging: {{ drag }}</p>
<style>
.list-group { min-height: 50px; border: 1px solid #eee; padding: 10px; margin-bottom: 20px; }
.list-group-item { padding: 10px; margin-bottom: 5px; background-color: #f9f9f9; border: 1px solid #ddd; cursor: grab; }
.list-group-item:active { cursor: grabbing; }
</style>
</div>
`,
});