How to Implement Drag-and-Drop Functionality with the Element UI Transfer Component

The Element UI Transfer component does not ship with built-in drag-and-drop capabilities, but you can extend it by leveraging the internal draggable.js helper and manually syncing the value array when items are dropped between panels.

The Transfer component in the ElemeFE/element repository provides a robust interface for moving items between two lists, yet it only supports button-based transfers by default. While the source code contains reusable drag utilities used by other components, implementing drag-and-drop functionality with the Transfer component requires connecting the draggable.js helper to the panel render logic. This guide demonstrates how to wire the internal drag system into main.vue and transfer-panel.vue without breaking the existing API.

Understanding the Transfer Component Architecture

Before adding drag support, you must understand how the component handles data flow. The Transfer component consists of two identical TransferPanel instances managed by a wrapper component.

The key files are:

Step-by-Step Implementation Guide

Import the Draggable Helper

The Element UI library includes a low-level drag utility originally built for the color picker. Import it into your custom wrapper or modified panel component:

import draggable from 'element-ui/packages/color-picker/src/draggable';

Attach Drag Handlers to Panel Items

Since transfer-panel.vue renders items as checkboxes inside .el-transfer-panel__item containers, you must initialize the draggable behavior after the DOM updates. In your custom panel extension or mounted hook, query these elements and bind the helper:

mounted () {
  this.$nextTick(() => {
    this.$el.querySelectorAll('.el-transfer-panel__item').forEach(itemEl => {
      const key = itemEl.getAttribute('data-key');
      draggable(itemEl, {
        start: () => { this.draggedKey = key; },
        end: e => { this.handleDrop(e); }
      });
    });
  });
}

Ensure your template binds the key attribute: :data-key="item[keyProp]".

Detect Drop Targets and Emit Events

When a drag ends, determine whether the mouse released over the opposite panel. The draggable helper provides an end callback containing mouse coordinates. Emit a custom drag-drop event with the source panel, destination panel, and item key:

handleDrop (event, targetPanel) {
  const target = targetPanel === this.$refs.leftPanel ? 'left' : 'right';
  const from = target === 'left' ? 'right' : 'left';
  this.$emit('drag-drop', { from, to: target, key: this.draggedKey });
  this.draggedKey = null;
}

Update the Value Array in main.vue

In packages/transfer/src/main.vue, listen for the drag-drop event on both panels and implement the logic to add or remove keys from the bound array:

methods: {
  handleDragDrop ({ from, to, key }) {
    let newValue = this.value.slice();
    if (from === 'left' && to === 'right') {
      newValue.push(key);
    } else if (from === 'right' && to === 'left') {
      const idx = newValue.indexOf(key);
      if (idx > -1) newValue.splice(idx, 1);
    }
    this.$emit('input', newValue);
    this.$emit('change', newValue, to, [key]);
  }
}

This mirrors the behavior of the native addToRight and addToLeft methods while preserving reactivity.

Complete Working Example

The following wrapper component demonstrates a production-ready implementation that integrates drag interactions while preserving the component's original API:

<template>
  <el-transfer
    v-model="value"
    :data="list"
    :props="{key: 'id', label: 'name'}"
    @drag-drop="onDragDrop">
    <template #left-footer>
      <span>Drag items here →</span>
    </template>
    <template #right-footer>
      <span>← Drop items here</span>
    </template>
  </el-transfer>
</template>

<script>
import Transfer from 'element-ui/packages/transfer';
import draggable from 'element-ui/packages/color-picker/src/draggable';

export default {
  components: { Transfer },
  data () {
    return {
      value: [],
      list: [
        { id: 1, name: 'Option 1' },
        { id: 2, name: 'Option 2' },
        { id: 3, name: 'Option 3' }
      ],
      draggedKey: null
    };
  },
  mounted () {
    this.$nextTick(this.enableDrag);
  },
  methods: {
    enableDrag () {
      const panels = [this.$refs.leftPanel, this.$refs.rightPanel];
      panels.forEach(panel => {
        if (!panel) return;
        panel.$el.querySelectorAll('.el-transfer-panel__item').forEach(el => {
          const key = el.getAttribute('data-key');
          draggable(el, {
            start: () => { this.draggedKey = key; },
            end: event => this.handleDrop(event, panel)
          });
        });
      });
    },
    handleDrop (event, targetPanel) {
      const target = targetPanel === this.$refs.leftPanel ? 'left' : 'right';
      const from = target === 'left' ? 'right' : 'left';
      this.onDragDrop({ from, to: target, key: this.draggedKey });
      this.draggedKey = null;
    },
    onDragDrop ({ from, to, key }) {
      const newVal = this.value.slice();
      if (to === 'right') newVal.push(key);
      else {
        const idx = newVal.indexOf(key);
        if (idx > -1) newVal.splice(idx, 1);
      }
      this.value = newVal;
    }
  }
};
</script>

Summary

  • The Transfer component relies on packages/transfer/src/main.vue and packages/transfer/src/transfer-panel.vue to manage item movement via the addToLeft and addToRight methods.
  • The internal packages/color-picker/src/draggable.js helper provides native mouse-based dragging without external dependencies.
  • To implement drag-and-drop functionality with the Transfer component, bind the helper to each .el-transfer-panel__item, track the dragged key, and emit custom events to update the value array.
  • Always synchronize the bound array using this.$emit('input', newValue) to maintain reactive state consistency with the original component design.

Frequently Asked Questions

Does Element UI Transfer support drag and drop out of the box?

No. As implemented in the ElemeFE/element repository, the Transfer component only supports moving items via the center action buttons. The component architecture separates concerns between the panel display and movement logic, requiring manual integration of the draggable.js helper or a third-party library to enable drag interactions.

Which files should I modify to add drag and drop to the Transfer component?

You should extend or wrap packages/transfer/src/transfer-panel.vue to attach drag listeners to the checkbox items, and modify or intercept events in packages/transfer/src/main.vue to handle the actual data transfer. The reusable logic lives in packages/color-picker/src/draggable.js, which you can import without altering the library source if you create a wrapper component.

Can I use a third-party drag-and-drop library instead of the internal draggable helper?

Yes. While the internal draggable.js utility provides a lightweight, dependency-free solution, you can substitute libraries like SortableJS or Vue.Draggable. Replace the enableDrag method in your wrapper component to initialize the third-party library on the .el-transfer-panel__list container, then map the library's move events to the same handleDragDrop logic that updates the value array.

How do I preserve keyboard accessibility when implementing custom drag and drop?

Keep the default action buttons visible as a fallback for keyboard users, or provide explicit keyboard shortcuts that trigger the same handleDragDrop method. Since the native Transfer component uses standard checkboxes and buttons, maintaining these elements ensures WCAG compliance even when drag interactions are layered on top.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →