# How Vue-Element-Admin Implements Drag-and-Drop Table Functionality Using vuedraggable

> Learn how Vue-Element-Admin implements drag-and-drop table functionality. Discover how vuedraggable enhances list manipulation and state synchronization for dynamic interfaces.

- Repository: [花裤衩/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Vue-Element-Admin implements drag-and-drop tables by wrapping the `vuedraggable` component around list data, binding arrays to the `:list` prop, enabling cross-list dragging with the `group` attribute, and synchronizing state through custom helper methods.**

The **PanJiaChen/vue-element-admin** repository provides a production-ready implementation of drag-and-drop table functionality using vuedraggable in the `DndList` component. This pattern allows users to reorder items within a single list or move items between two separate lists while maintaining reactive state synchronization.

## Core Implementation Pattern in Vue-Element-Admin

The implementation follows a structured component-based architecture that leverages `vuedraggable` (version 2.20.0 as specified in [`package.json`](https://github.com/PanJiaChen/vue-element-admin/blob/main/package.json)) to handle the underlying Sortable.js logic.

### Importing and Registering vuedraggable

The component begins by importing the draggable library and registering it locally. This encapsulation ensures the drag-and-drop functionality remains modular and reusable across the application.

```javascript
import draggable from 'vuedraggable'

export default {
  name: 'DndList',
  components: { draggable },
  props: {
    list1: {
      type: Array,
      default: () => []
    },
    list2: {
      type: Array,
      default: () => []
    }
  }
}

```

### Binding Data with the `:list` Prop

The `vuedraggable` component requires a reactive array bound to the `:list` prop. In [`src/components/DndList/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/components/DndList/index.vue), two separate draggable instances manage `list1` and `list2` respectively. This binding ensures that any reordering or movement automatically mutates the source arrays.

```html
<draggable :list="list1" group="article" class="dragArea">
  <!-- list1 items -->
</draggable>

<draggable :list="list2" group="article" class="dragArea">
  <!-- list2 items -->
</draggable>

```

### Enabling Cross-List Dragging with `group`

To allow items to move between the two lists, both `draggable` instances share the same `group` name (`article`). This configuration creates a shared drag context where elements can transition from `list1` to `list2` and vice versa while maintaining the reactive data flow.

### Handling Firefox Compatibility with `setData`

The implementation includes a workaround for a known Firefox bug where the drag operation fails without data in the `dataTransfer` object. The `setData` method injects empty text data to satisfy the browser requirement.

```javascript
methods: {
  setData(dataTransfer) {
    dataTransfer.setData('Text', '')
  }
}

```

## Custom Helper Methods for State Synchronization

Beyond basic dragging, the `DndList` component provides explicit control methods for programmatically moving items between lists. These helpers ensure data consistency when users prefer click-based interactions over drag gestures.

The `deleteEle` method removes an item from the left list (`list1`) and conditionally prepends it to the right list (`list2`) if not already present:

```javascript
deleteEle(ele) {
  const idx = this.list1.findIndex(item => item.id === ele.id)
  if (idx !== -1) {
    this.list1.splice(idx, 1)
  }
  if (!this.list2.some(item => item.id === ele.id)) {
    this.list2.unshift(ele)
  }
}

```

Conversely, the `pushEle` method moves items from the right list back to the left:

```javascript
pushEle(ele) {
  const idx = this.list2.findIndex(item => item.id === ele.id)
  if (idx !== -1) {
    this.list2.splice(idx, 1)
  }
  if (!this.list1.some(item => item.id === ele.id)) {
    this.list1.push(ele)
  }
}

```

## Styling Drag States and Visual Feedback

The component uses scoped SCSS to provide visual cues during drag operations. It leverages Sortable.js's automatic class injection to style elements being dragged or targeted as drop zones.

```scss
.sortable-chosen {
  background: #4ab7bd !important;
  color: #fff;
}

.sortable-ghost {
  opacity: 0.5;
  background: #c8ebfb;
}

```

The `.sortable-chosen` class applies to the actively dragged element, while `.sortable-ghost` styles the placeholder indicating where the item will drop.

## Complete Code Example

The following implementation demonstrates the full pattern used in [`src/components/DndList/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/components/DndList/index.vue):

```vue
<template>
  <div class="dndList">
    <div class="dndList-list">
      <h3>Draggable List 1</h3>
      <draggable 
        :list="list1" 
        group="article" 
        class="dragArea"
        :set-data="setData"
      >
        <div 
          v-for="element in list1" 
          :key="element.id" 
          class="list-complete-item"
        >
          <div class="list-complete-item-handle">
            {{ element.id }} {{ element.title }}
          </div>
          <div style="position:absolute;right:0px;">
            <span 
              style="float: right;margin-top: -20px;margin-right:5px;" 
              @click="deleteEle(element)"
            >
              <i style="color:#ff4949" class="el-icon-delete"/>
            </span>
          </div>
        </div>
      </draggable>
    </div>

    <div class="dndList-list">
      <h3>Draggable List 2</h3>
      <draggable 
        :list="list2" 
        group="article" 
        class="dragArea"
        :set-data="setData"
      >
        <div 
          v-for="element in list2" 
          :key="element.id" 
          class="list-complete-item"
        >
          <div 
            class="list-complete-item-handle2" 
            @click="pushEle(element)"
          >
            {{ element.id }} {{ element.title }}
          </div>
        </div>
      </draggable>
    </div>
  </div>
</template>

<script>
import draggable from 'vuedraggable'

export default {
  name: 'DndList',
  components: { draggable },
  props: {
    list1: {
      type: Array,
      default: () => []
    },
    list2: {
      type: Array,
      default: () => []
    }
  },
  methods: {
    deleteEle(ele) {
      const idx = this.list1.findIndex(item => item.id === ele.id)
      if (idx !== -1) {
        this.list1.splice(idx, 1)
      }
      if (!this.list2.some(item => item.id === ele.id)) {
        this.list2.unshift(ele)
      }
    },
    pushEle(ele) {
      const idx = this.list2.findIndex(item => item.id === ele.id)
      if (idx !== -1) {
        this.list2.splice(idx, 1)
      }
      if (!this.list1.some(item => item.id === ele.id)) {
        this.list1.push(ele)
      }
    },
    setData(dataTransfer) {
      dataTransfer.setData('Text', '')
    }
  }
}
</script>

```

## Summary

- **Vue-Element-Admin** implements drag-and-drop table functionality using vuedraggable by wrapping the `<draggable>` component around list data in [`src/components/DndList/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/components/DndList/index.vue).
- The **`:list`** prop creates two-way binding with reactive arrays, while the **`group`** attribute enables cross-list dragging between multiple containers.
- **Custom helper methods** (`deleteEle` and `pushEle`) provide programmatic control for moving items between lists while preventing duplicates.
- A **`setData`** workaround ensures Firefox compatibility by populating the `dataTransfer` object during drag operations.
- **Scoped SCSS** styles for `.sortable-chosen` and `.sortable-ghost` provide visual feedback during drag interactions.

## Frequently Asked Questions

### How does vuedraggable integrate with Vue-Element-Admin's table components?

Vuedraggable integrates as a wrapper component around standard list rendering logic. In [`src/components/DndList/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/components/DndList/index.vue), the `<draggable>` element replaces a static container, binding directly to the `list1` and `list2` arrays via the `:list` prop. This integration maintains Vue's reactivity system while adding Sortable.js drag capabilities without modifying the underlying Element UI table components.

### What is the purpose of the `group` attribute in the DndList component?

The `group` attribute creates a shared drag context between multiple draggable containers. By assigning both lists the group name `article`, the configuration allows items to move from `list1` to `list2` and vice versa. Without this attribute, dragging would be restricted to reordering within a single list. This pattern supports complex workflows like task boards or permission assignment interfaces.

### How does the implementation handle browser-specific bugs like the Firefox drag issue?

The implementation includes a `setData` method that addresses a Firefox-specific requirement where the drag operation fails if the `dataTransfer` object contains no data. The method injects an empty string via `dataTransfer.setData('Text', '')` during the drag start event. This workaround ensures consistent cross-browser behavior without affecting the visual presentation or data payload of the dragged items.

### Can the drag-and-drop pattern be extended to more than two lists?

Yes, the pattern scales to multiple lists by adding additional `<draggable>` components with the same `group` attribute value. Each list binds to a distinct array prop (e.g., `list3`, `list4`), and the helper methods can be refactored to accept target list parameters dynamically. The existing architecture in `DndList` demonstrates the reactive principles required for multi-list implementations, though the current component specifically handles two lists for the common "todo/done" workflow pattern.