npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

vue-draggable-next

v2.3.0

Published

Vue 3 drag-and-drop component based on Sortable.js - Touch-friendly, lightweight, and TypeScript ready

Readme

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

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | Array | [] | Array to be synchronized with drag-and-drop (use with v-model) | | list | Array | [] | Alternative to modelValue, directly mutates the array | | itemKey | String\|Function | undefined | Key to use for tracking items (recommended for better performance) | | tag | String | 'div' | HTML tag for the root element | | component | String | null | Vue component name to use as root element | | componentData | Object | null | Props/attrs to pass to the component | | clone | Function | (item) => item | Function to clone items when dragging | | move | Function | null | Function to control move operations | | group | String\|Object | undefined | Sortable group options | | sort | Boolean | true | Enable sorting within the list | | disabled | Boolean | false | Disable drag and drop | | animation | Number | 0 | Animation speed (ms) | | ghostClass | String | '' | CSS class for the ghost element | | chosenClass | String | '' | CSS class for the chosen element | | dragClass | String | '' | CSS class for the dragging element |

Events

| Event | Description | Payload | |-------|-------------|---------| | @change | Fired when the list changes | { added?, removed?, moved? } | | @start | Dragging started | SortableEvent | | @end | Dragging ended | SortableEvent | | @add | Item added from another list | SortableEvent | | @remove | Item removed to another list | SortableEvent | | @update | Item order changed | SortableEvent | | @sort | Any change to the list | SortableEvent | | @choose | Item is chosen | SortableEvent | | @unchoose | Item is unchosen | SortableEvent |

🎯 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