Spectrum 2 is now available! Check out the migration docs.

Drag and Drop

This page describes how to enable drag and drop functionality for the various React Spectrum components that support it.

Introduction#


Drag and drop is a common UI interaction that allows users to transfer data between two locations by directly moving a visual representation on screen. It is a flexible, efficient, and intuitive way for users to perform a variety of tasks, and is widely supported across both desktop and mobile operating systems. In addition to the standard mouse and touch interactions, React Spectrum also implements keyboard and screen reader accessible alternatives for drag and drop to enable all users to perform these tasks.

Drag and Drop Concepts#


Before we dive into how to enable drag and drop in React Spectrum, let's touch briefly on the terminology and concepts of drag and drop. In a drag and drop operation, there is a drag source and a drop target. The drag source is the starting location of your dragged data and the drop target is its intended destination. The dragged data is made up of one or more drag items, each of which contains information specific to their original item within the drag source.

PicturesMusicDocumentsVideosApplicationsFolderDownloadsSharedMusic2Drag previewDrag sourceDrop target

A drag item contains several pieces of information: the type of the data, the item's kind, and the actual data itself. The type of a drag item can be one of the common mime types or a custom type specific to your application. Multiple types can be attached to a single drag item so that the item's data can be provided in different formats for interoperability with various drop targets. For example, an image could be represented by an image/jpeg type and thus be recognized as a JPEG by a file upload drop target but also have a plain/text type that allows the image's file name to be communicated to a text input drop target. In addition, there are two kinds of items: string items include inline data in the form of a Unicode string, and file items include a reference to a file from the user's computer.

There are several drop operations that can be performed in a drag and drop operation: "move", "copy", "link", and "cancel". A "move" operation indicates that the dragged data will be moved from its source location to the target location. A "copy" operation indicates that the dragged data will be copied to the target destination. A "link" operation indicates that there will be a relationship established between the source and target locations. Finally, a "cancel" operation indicates that the drag and drop operation will be canceled, resulting in no changes made to the source or target. The drag source can specify what drop operations are allowed for its data, allowing the drop target to decide what operation to perform, using the restrictions set by the drag source as a guideline.

DocumentsApplicationsPicturesDownloadsDragged FolderApplicationsPicturesDownloadsDocumentsDragged FolderDragged FolderPicturesDocumentsDownloadsDragged Folder
The "root", "on", and "between" drop positions.

Collection components, such as ListView and TableView, support multiple drop positions. The component may support a "root" drop position, allowing items to be dropped on the collection as a whole. It may also support "on" drop positions, such as when dropping into a folder in a list. If the collection allows reordering of its items, it could support "between" drop positions, allowing the user to insert or move items between other items. Any number of these drop positions can be allowed at the same time and the component can use the types of the dragged items to selectively allow or disallow certain positions.

Interaction modes#

There are several interaction modes that need to be considered for drag and drop. When using a mouse, you can click an item and drag by holding the mouse button down and moving the pointer. A drop can be performed by releasing the mouse button or canceled by the Esc key. A similar interaction can be performed via touch, with a drag initiated via a long press and a drop performed by removing your finger from the screen. In both cases, selecting and dragging an item is often accompanied by a drag preview. The drag preview is a smaller version of the dragged item that follows the cursor or touch point. When multiple items are dragged at once, the drag preview displays a stack of items instead, accompanied by a badge reflecting the total number of dragged items. Drop targets are visually highlighted when dragged over, and the desired drop operation can be controlled via modifier key presses or drop activations via hovering over the drop target for a period of time.

Documents5
A custom drag preview representing multiple drag items.

For keyboards, copy and paste shortcuts have traditionally been the alternative method to drag and drop. This comes with many limitations as it is often hard to know where pasting is allowed and difficult to control the exact positioning of the pasted items. Touch screen readers are even more limited in their ability to perform these operations since they often lack access to a keyboard and thus cannot copy paste in the same manner.

React Spectrum attempts to resolve the above limitations by providing interactive drag affordances that bring a user into drag and drop mode when triggered via keyboard or screen reader virtual click. To perform a drag and drop operation via a keyboard, first select the items to be dragged by focusing the row and pressing Space. You can then start the drag operation by moving focus to the drag handle on any of the selected rows via the arrow keys and hitting Enter or Space. Once a drag operation is started, you will be automatically brought to the first valid drop target. Tab can then be used to cycle through other valid drop targets. For collection components like the ListView above, Tab will move you on or off the overall component itself whereas ArrowUp and ArrowDown will cycle through the valid drop targets within the component itself. Hitting Enter will then confirm the drop operation on the focused drop target. To cancel a drag operation, you can hit Esc at any time.

PandaKangarooDogCat
A focusable drag affordance to initiate keyboard and screen reader drag and drop.

For screen readers, please follow the custom instructions announced when focusing the row's drag handle to begin a drag operation. For screen readers on mobile devices, swiping left and right will move you between valid drop targets and double tapping will confirm a drop operation. Go ahead and try out drag and drop in the example below!

Examples#


Creating the draggable list#

For the first ListView in the example above, we want to make the rows draggable and have the dragged rows removed from the list upon a successful drop. To accomplish this, we first want to set up the initial list of items for our draggable ListView via useListData so that we have access to some helper methods to modify the list of items on the fly. Note that this is completely optional and is not required to enable drag and drop in React Spectrum. You may substitute useListData with useAsyncList or with any other state management solution.

let list = useListData({
  initialItems: [
    {id: 'a', type: 'file', name: 'Adobe Photoshop'},
    {id: 'b', type: 'file', name: 'Adobe XD'},
    {id: 'c', type: 'file', name: 'Adobe Dreamweaver'},
    {id: 'd', type: 'file', name: 'Adobe InDesign'},
    {id: 'e', type: 'file', name: 'Adobe Connect'}
  ]
});
let list = useListData({
  initialItems: [
    {id: 'a', type: 'file', name: 'Adobe Photoshop'},
    {id: 'b', type: 'file', name: 'Adobe XD'},
    {id: 'c', type: 'file', name: 'Adobe Dreamweaver'},
    {id: 'd', type: 'file', name: 'Adobe InDesign'},
    {id: 'e', type: 'file', name: 'Adobe Connect'}
  ]
});
let list = useListData({
  initialItems: [
    {
      id: 'a',
      type: 'file',
      name:
        'Adobe Photoshop'
    },
    {
      id: 'b',
      type: 'file',
      name: 'Adobe XD'
    },
    {
      id: 'c',
      type: 'file',
      name:
        'Adobe Dreamweaver'
    },
    {
      id: 'd',
      type: 'file',
      name:
        'Adobe InDesign'
    },
    {
      id: 'e',
      type: 'file',
      name:
        'Adobe Connect'
    }
  ]
});

Next, we need to specify the data associated with each dragged item by returning an array from the getItems function. As described above in the concepts section, each item includes a mapping of drag types to serialized data. In this case, we look up the information for each dragged item and serialize it, mapping it to a custom item type. This information will be processed and provided to the drop target's drop handlers on drop.

let getItems = (keys) => [...keys].map(key => {
  let item = list.getItem(key);
  return {
    'adobe-app': JSON.stringify(item)
  };
})
let getItems = (keys) => [...keys].map(key => {
  let item = list.getItem(key);
  return {
    'adobe-app': JSON.stringify(item)
  };
})
let getItems = (keys) =>
  [...keys].map(
    (key) => {
      let item = list
        .getItem(key);
      return {
        'adobe-app': JSON
          .stringify(
            item
          )
      };
    }
  );

We also create an onDragEnd event handler for useDragAndDrop that handles removing the dragged items from the draggable list upon a successful drop operation. Note how we use the .remove method provided by useListData to remove the dropped items from our list.

let onDragEnd = (e) => {
  if (e.dropOperation === 'move') {
    list.remove(...e.keys);
  }
}
let onDragEnd = (e) => {
  if (e.dropOperation === 'move') {
    list.remove(...e.keys);
  }
}
let onDragEnd = (e) => {
  if (
    e.dropOperation ===
      'move'
  ) {
    list.remove(
      ...e.keys
    );
  }
};

Finally, we provide our getItems and onDragEnd functions as options to useDragAndDrop, providing us with a set of dragAndDropHooks that we can pass to our ListView directly. Below is what our draggable ListView would look like after combining everything together. For more info on getItems and onDragEnd, see the props section below and the React Aria docs.

import {useDragAndDrop} from '@adobe/react-spectrum';
import {Item, ListView, useListData} from '@adobe/react-spectrum';

function DraggableList() {
  let list = useListData({
    initialItems: [
      { id: 'a', type: 'file', name: 'Adobe Photoshop' },
      { id: 'b', type: 'file', name: 'Adobe XD' },
      { id: 'c', type: 'file', name: 'Adobe Dreamweaver' },
      { id: 'd', type: 'file', name: 'Adobe InDesign' },
      { id: 'e', type: 'file', name: 'Adobe Connect' }
    ]
  });

  let { dragAndDropHooks } = useDragAndDrop({
    getItems: (keys) =>
      [...keys].map((key) => {
        let item = list.getItem(key);
        return {
          'adobe-app': JSON.stringify(item)
        };
      }),
    onDragEnd: (e) => {
      if (e.dropOperation === 'move') {
        list.remove(...e.keys);
      }
    }
  });

  return (
    <ListView
      aria-label="Draggable list view example"
      width="size-3600"
      height="size-3600"
      selectionMode="multiple"
      items={list.items}
      dragAndDropHooks={dragAndDropHooks}
    >
      {(item) => (
        <Item textValue={item.name}>
          {item.name}
        </Item>
      )}
    </ListView>
  );
}
import {useDragAndDrop} from '@adobe/react-spectrum';
import {
  Item,
  ListView,
  useListData
} from '@adobe/react-spectrum';

function DraggableList() {
  let list = useListData({
    initialItems: [
      { id: 'a', type: 'file', name: 'Adobe Photoshop' },
      { id: 'b', type: 'file', name: 'Adobe XD' },
      { id: 'c', type: 'file'