This post explains the basics of drag and drop.


Eric Bidelman [X](https://twitter.com/ebidel) [GitHub](https://github.com/ebidel) [Homepage](http://ericbidelman.com/) ![Rachel Andrew](https://web.dev/images/authors/rachelandrew.jpg) Rachel Andrew Technical Writer [GitHub](https://github.com/rachelandrew) [LinkedIn](https://www.linkedin.com/in/rachelandrew) [Mastodon](https://front-end.social/@rachelandrew) [Bluesky](https://bsky.app/profile/rachelandrew.bsky.social) [Homepage](https://rachelandrew.co.uk)

<br />

<iframe src="https:///frame/articles/drag-and-drop/index_34ed5025315b143667d8fcd48e31daca1f22369fecabf4a2d4c9ef9d88a2faf1.frame" class="framebox inherit-locale " allow="clipboard-write https://" allowfullscreen is-upgraded></iframe>

In most browsers, text selections, images, and links are draggable by default.
For example, if you drag a link on a web page, you see a small box with a
title and URL that you can drop on the address bar or the desktop to create a
shortcut or navigate to the link. To make other types of content draggable, you
need to use the [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API).

> [!CAUTION]
> **Caution:** This page has one or more broken demos, as [Glitch has ended hosting](https://blog.glitch.com/post/changes-are-coming-to-glitch).

## Create draggable content

To make an object draggable, set `draggable=true` on that element. Just about
anything can be drag-enabled, including images, files, links, files, or any
markup on your page.

The following example creates an interface to rearrange columns that have been
laid out with CSS Grid. The basic markup for the columns looks like this, with
the `draggable` attribute for each column set to `true`:

    <div class="container">
      <div draggable="true" class="box">A</div>
      <div draggable="true" class="box">B</div>
      <div draggable="true" class="box">C</div>
    </div>

Here's the CSS for the container and box elements. The only CSS related to the
drag feature is the [`cursor: move`](https://developer.mozilla.org/docs/Web/CSS/cursor)
property. The rest of the code controls the layout and styling of the container
and box elements.

    .container {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      gap: 10px;
    }

    .box {
      border: 3px solid #666;
      background-color: #ddd;
      border-radius: .5em;
      padding: 10px;
      cursor: move;
    }

At this point you can drag the items, but nothing else happens. To add
behavior, you need to use the JavaScript API.

## Listen for dragging events

To monitor the drag process, you can listen for any of the following events:

- [`dragstart`](https://developer.mozilla.org/docs/Web/API/Document/dragstart_event)
- [`drag`](https://developer.mozilla.org/docs/Web/API/Document/drag_event)
- [`dragenter`](https://developer.mozilla.org/docs/Web/API/Document/dragenter_event)
- [`dragleave`](https://developer.mozilla.org/docs/Web/API/Document/dragleave_event)
- [`dragover`](https://developer.mozilla.org/docs/Web/API/Document/dragover_event)
- [`drop`](https://developer.mozilla.org/docs/Web/API/Document/drop_event)
- [`dragend`](https://developer.mozilla.org/docs/Web/API/Document/dragend_event)

To handle the drag flow, you need some kind of source element (where the drag
starts), the data payload (the thing being dragged), and a target (an area to
catch the drop). The source element can be almost any kind of element. The
target is the drop zone or set of drop zones that accepts the data the user is
trying to drop. Not all elements can be targets. For example, your target can't
be an image.

## Start and end a drag sequence

After you define `draggable="true"` attributes on your content, attach a
`dragstart` event handler to start the drag sequence for each column.

This code sets the column's opacity to 40% when the user starts dragging it,
then return it to 100% when the dragging event ends.

    function handleDragStart(e) {
      this.style.opacity = '0.4';
    }

    function handleDragEnd(e) {
      this.style.opacity = '1';
    }

    let items = document.querySelectorAll('.container .box');
    items.forEach(function (item) {
      item.addEventListener('dragstart', handleDragStart);
      item.addEventListener('dragend', handleDragEnd);
    });

The result can be seen in the following Glitch demo. Drag an item, and its
opacity changes. Because the source element has the `dragstart` event, setting
`this.style.opacity` to 40% gives the user visual feedback that that element is
the current selection being moved. When you drop the item, the source element
returns to 100% opacity, even though you haven't defined the drop behavior yet.
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi" loading="lazy" src="https://glitch.com/embed/#!/embed/simple-drag-and-drop-1?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=style.css&amp;previewSize=100" style="height: 100%; width: 100%; border: 0;" title="simple-drag-and-drop-1 on Glitch"></iframe>

## Add additional visual cues

To help the user understand how to interact with your interface, use the
`dragenter`, `dragover` and `dragleave` event handlers. In this example, the
columns are drop targets in addition to being draggable. Help the user to
understand this by making the border dashed when they hold a dragged item over a
column. For example, in your CSS, you might create an `over` class for
elements that are drop targets:

    .box.over {
      border: 3px dotted #666;
    }

Then, in your JavaScript, set up the event handlers, add the `over` class when
the column is dragged over, and remove it when the dragged element leaves. In
the `dragend` handler we also make sure to remove the classes at the end of the
drag.

    document.addEventListener('DOMContentLoaded', (event) => {

      function handleDragStart(e) {
        this.style.opacity = '0.4';
      }

      function handleDragEnd(e) {
        this.style.opacity = '1';

        items.forEach(function (item) {
          item.classList.remove('over');
        });
      }

      function handleDragOver(e) {
        e.preventDefault();
        return false;
      }

      function handleDragEnter(e) {
        this.classList.add('over');
      }

      function handleDragLeave(e) {
        this.classList.remove('over');
      }

      let items = document.querySelectorAll('.container .box');
      items.forEach(function(item) {
        item.addEventListener('dragstart', handleDragStart);
        item.addEventListener('dragover', handleDragOver);
        item.addEventListener('dragenter', handleDragEnter);
        item.addEventListener('dragleave', handleDragLeave);
        item.addEventListener('dragend', handleDragEnd);
        item.addEventListener('drop', handleDrop);
      });
    });

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi" loading="lazy" src="https://glitch.com/embed/#!/embed/simple-drag-drop2?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=dnd.js&amp;previewSize=100" style="height: 100%; width: 100%; border: 0;" title="simple-drag-drop2 on Glitch"></iframe>

There are a couple of points worth covering in this code:

- The [default action](https://developer.mozilla.org/docs/Web/API/Document/dragover_event)
  for `dragover` event is to set the `dataTransfer.dropEffect` property to
  `"none"`. The `dropEffect` property is covered later on this page. For now,
  just know that it prevents the `drop` event from firing. To override this
  behavior, call `e.preventDefault()`. Another good practice is to return
  `false` in that same handler.

- The `dragenter` event handler is used to toggle the `over` class instead of
  `dragover`. If you use `dragover`, the event fires repeatedly while the user
  holds the dragged item over a column, causing the CSS class to toggle
  repeatedly. This makes the browser do a lot of unnecessary rendering work,
  which can affect the user experience. We strongly recommend minimizing
  redraws, and if you need to use `dragover`, consider
  [throttling or debouncing your event listener](https://css-tricks.com/debouncing-throttling-explained-examples/).

## Complete the drop

To process the drop, add an event listener for the `drop` event. In the `drop`
handler, you'll need to prevent the browser's default behavior for drops, which
is typically some sort of annoying redirect. To do this, call `e.stopPropagation()`.

    function handleDrop(e) {
      e.stopPropagation(); // stops the browser from redirecting.
      return false;
    }

Be sure to register the new handler alongside the other handlers:

      let items = document.querySelectorAll('.container .box');
      items.forEach(function(item) {
        item.addEventListener('dragstart', handleDragStart);
        item.addEventListener('dragover', handleDragOver);
        item.addEventListener('dragenter', handleDragEnter);
        item.addEventListener('dragleave', handleDragLeave);
        item.addEventListener('dragend', handleDragEnd);
        item.addEventListener('drop', handleDrop);
      });

If you run the code at this point, the item doesn't drop to the new location. To
make that happen, use the [`DataTransfer`](https://developer.mozilla.org/docs/Web/API/DataTransfer)
object.

The `dataTransfer` property holds the data sent in a drag action. `dataTransfer`
is set in the `dragstart` event and read or handled in the drop event. Calling
`e.dataTransfer.setData(mimeType, dataPayload)` lets you set the object's MIME
type and data payload.

In this example, we're going to let users rearrange the order of the columns.
To do that, first you need to store the source element's HTML when the drag
starts:

<br />

<br />

    function handleDragStart(e) {
      this.style.opacity = '0.4';

      dragSrcEl = this;

      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/html', this.innerHTML);
    }

In the `drop` event, you process the column drop by setting the source column's
HTML to the HTML of the target column that you dropped the data on. This
includes checking that the user isn't dropping back onto the same column they
dragged from.

    function handleDrop(e) {
      e.stopPropagation();

      if (dragSrcEl !== this) {
        dragSrcEl.innerHTML = this.innerHTML;
        this.innerHTML = e.dataTransfer.getData('text/html');
      }

      return false;
    }

You can see the result in the following demo. For this to work, you'll need a
desktop browser. The Drag and Drop API isn't supported on mobile. Drag and
release the A column on top of the B column and notice how they change places:
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi" loading="lazy" src="https://glitch.com/embed/#!/embed/simple-drag-drop?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=dnd.js&amp;previewSize=100" style="height: 100%; width: 100%; border: 0;" title="simple-drag-drop on Glitch"></iframe>

## More dragging properties

The `dataTransfer` object exposes properties to provide visual feedback to the
user during the drag process and control how each drop target responds to a
particular data type.

- [`dataTransfer.effectAllowed`](https://developer.mozilla.org/docs/Web/API/DataTransfer/effectAllowed) restricts what 'type of drag' the user can perform on the element. It's used in the drag-and-drop processing model to initialize the `dropEffect` during the `dragenter` and `dragover` events. The property can have the following values: `none`, `copy`, `copyLink`, `copyMove`, `link`, `linkMove`, `move`, `all`, and `uninitialized`.
- [`dataTransfer.dropEffect`](https://developer.mozilla.org/docs/Web/API/DataTransfer/dropEffect) controls the feedback that the user gets during the `dragenter` and `dragover` events. When the user holds their pointer over a target element, the browser's cursor indicates what type of operation is going to take place, such as a copy or a move. The effect can take one of the following values: `none`, `copy`, `link`, `move`.
- [`e.dataTransfer.setDragImage(imgElement, x, y)`](https://developer.mozilla.org/docs/Web/API/DataTransfer/setDragImage) means that instead of using the browser's default 'ghost image' feedback, you can set a drag icon.

## File upload

This example uses a column as both the drag source and drag target. This
might happen in a UI that asks the user to rearrange items. In some situations,
the drag target and source might be different element types, as in an interface
where the user needs to select one image as the main image for a product by
dragging the selected image onto a target.

Drag and Drop is frequently used to let users drag items from their desktop into
an application. The main difference is in your `drop` handler. Instead of using
`dataTransfer.getData()` to access the files, their data is contained in the
`dataTransfer.files` property:

    function handleDrop(e) {
      e.stopPropagation(); // Stops some browsers from redirecting.
      e.preventDefault();

      var files = e.dataTransfer.files;
      for (var i = 0, f; (f = files[i]); i++) {
        // Read the File objects in this FileList.
      }
    }

You can find more information about this in
[Custom drag-and-drop](https://web.dev/articles/read-files#select-dnd).

## More resources

- [The Drag and Drop Specification](https://html.spec.whatwg.org/multipage/dnd.html#dnd)
- [MDN HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API)
- [How To Make A Drag-and-Drop File Uploader With Vanilla JavaScript](https://www.smashingmagazine.com/2018/01/drag-drop-file-uploader-vanilla-js/)
- [Creating a Parking Game With the HTML Drag and Drop API](https://css-tricks.com/creating-a-parking-game-with-the-html-drag-and-drop-api/)
- [How To Use The HTML Drag-and-Drop API In React](https://www.smashingmagazine.com/2020/02/html-drag-drop-api-react/)