Drag and drop with HTML5 and JavaScript

Drag and drop with HTML5 and JavaScript

Learn how to move elements between separate lists with vanilla JS and HTML5 drag and drop API

HTML5 provides a way to grab and move elements around the DOM and drop to them a specific location. The fact that this can be easily done with vanilla JS, without using any third-party libraries, makes it even more cool.

So, let's dive in. 👇

I will be using Bootstrap 5 for styling and icons. Add the following lines of code inside the <head> tag of your index.html:

<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drag & drop</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">

Inside your <body> tag add the following HTML:

<div class="container py-5">
        <h2 class="my-3">Drag and drop between lists</h2>

        <div class="row">
            <div class="col-6">
                <div id="firstList" class="parent">
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="tomato">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                tomato
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="potato">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                potato
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="cucumber">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                cucumber
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="carrot">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                carrot
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="onion">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                onion
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="lettuce">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                lettuce
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="corn">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                corn
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-6">
            <div id="secondList" class="parent">
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="apple">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                apple
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="peach">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                peach
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="banana">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                banana
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="fig">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                fig
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="raspberry">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                raspberry
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="lemon">
                        <div class="card-body d-flex justify-content-between">    
                            <span>
                                lemon
                            </span>
                            <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
                                <i class="bi bi-x"></i>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
     <hr>
</div>

As you can see, we are setting the attribute draggable=true on some elements. This enables listening to drag and drop events.

Now let's include Bootstrap's JavaScript from CDN and our own JavaScript from app.js file. I am adding them at the bottom of my <body>.

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="app.js"></script>

Also let's create a simple form that will be used for creating new elements. These elements will not be assigned to any of the lists. Rather, these are unassigned elements and they can be dragged to one of the lists.

Now we have to write our JS code.

const unassignedItemsElement = document.querySelector("#unassignedItemsElement");
const addNewItem = document.querySelector("#addNewItem");
const form = document.querySelector("#form");
const lists = document.querySelectorAll(".parent");

form.addEventListener("submit", (e) => {
  e.preventDefault();
  addUnassignedElement(addNewItem.value);
  addNewItem.value = "";
});

function addUnassignedElement(element) {
  const id = `${element}-${Math.floor(Math.random() * 100) + 1}`;
  unassignedItemsElement.innerHTML += `
    <div class="card shadow-sm rounded my-1 draggable border-0" draggable="true" id="${id}">
        <div class="card-body d-flex justify-content-between">
          <span>
              ${element}
          </span>
          <button type="button" class="btn btn-sm btn-outline-danger rounded-circle remove-btn">
              <i class="bi bi-x"></i>
          </button>
        </div>
    </div>`;
  loopListItems();
}

function loopListItems() {
  lists.forEach((list) => {
    const listItems = list.querySelectorAll(".draggable");
    listItems.forEach((item) => {
      item.addEventListener("dragstart", onDragStart, false);
      item.addEventListener("dragover", onDragOver, false);
      item.addEventListener("drop", onDrop, false);
      item.querySelector(".remove-btn").addEventListener("click", removeItem);
    });
  });
}

loopListItems();

We are handing form submit event and we are adding a new item to the unassigned list based on the form input. Now we have to handle drag and drop events that are added in loopListItem() function. As you can see there are three events:

  • dragstart,
  • dragover,
  • drop

There are other events, but I am not using them since they are not important for our implementation right now. These are: dragenter, dragleave, dragend, drag.

let dragEl = null;

function onDragStart(event) {
  dragEl = this;
  event.dataTransfer.effectAllowed = "move";
  event.dataTransfer.setData("node", this.id);
  return false;
}

function onDragOver(event) {
  if (event.preventDefault()) {
    event.preventDefault();
  }

event.dataTransfer.dropEffect = "move";
return false;
}

function onDrop(event) {
  if (event.stopPropagation()) {
    event.stopPropagation();
  }

  if (dragEl !== this) {
    dragEl = this;
    if (this.parentNode !== unassignedItemsElement) {
       this.parentNode.insertBefore(document.getElementById(event.dataTransfer.getData("node")), this.nextSibling);
    }
  }

  dragEl = null;
  return false;
}

function removeItem(event) {
  this.parentNode.parentNode.remove();
}

The names of these events are pretty self-expanatory, so I named the functions in similar fashion. onDragStartis fired when the user starts dragging the element, onDragOver is fired when the dragged element is over the drop target and onDrop occurs when the dragged element is dropped on the drop target.

Inside these functions the this keyword holds the value of the context of the current event that is being handled. We are assigning its value to dragEl variable inside onDragStart and inside onDrop we are making sure that the element that is being currently dragged is actually different from the drop target element.

Another important thing to understand is dataTransfer object and its properties:

As the element is being dragged, dataTransferis basically carrying the necessary information about the element and the operation that is being executed.

I recommend reading more about the HTML5 Drag and Drop API because the docs provide more detailed info.

If you want to get the code that I used as an example, head over to this GitHub repo, or you can see it in action here.