How to Use the Drag and Drop API for Kanban Board Functionality: A Complete Implementation Guide

Implement a fully functional Kanban board by combining draggable="true" attributes with the native HTML Drag and Drop API event handlers (ondragstart, ondragover, ondrop), then persist the board state across sessions using localStorage.

The jisan-mia/dom-projects repository contains a production-ready Kanban demonstration that builds an interactive task board using only vanilla HTML, CSS, and JavaScript. This implementation leverages the browser's native Drag and Drop API to enable seamless card movement between columns while maintaining state persistence without external frameworks or libraries.

Setting Up Draggable Cards and Drop Zones

To initiate drag operations, each task card must be explicitly marked as draggable and wired to event handlers that record the element being moved.

Making Task Cards Draggable

In projects/kanban/index.html, each task is rendered as a list item with the draggable attribute and an ondragstart handler:

<li class="drag-item"
    draggable="true"
    ondragstart="drag(event)"
    contenteditable="true"
    onfocusout="updateItem(/*index*/, /*column*/)">
  Sample task
</li>

The drag() function in projects/kanban/script.js captures the dragged element:

function drag(e) {
  draggedItem = e.target;      // <li class="drag-item">
  isDragging = true;
}

Configuring Column Drop Zones

Each Kanban column uses an unordered list (<ul>) as the drop container. The markup attaches three critical event handlers to manage the drop lifecycle:

<ul class="dragndrop-item-list"
    id="backlog-list"
    ondrop="dropItem(event)"
    ondragover="allowDrop(event)"
    ondragenter="dragEnter(0)">
</ul>
  • ondragover: Fires continuously while dragging over the element
  • ondragenter: Fires once when entering the drop zone
  • ondrop: Fires when the user releases the draggable element

Handling Core Drag and Drop Events

The JavaScript implementation in projects/kanban/script.js coordinates the movement of cards between the four columns: Backlog, Progress, Complete, and On-Hold.

Allowing Drops and Providing Visual Feedback

By default, browsers prevent dropping onto elements. The allowDrop() function overrides this behavior:

function allowDrop(e) {
  e.preventDefault();         // Needed otherwise drop is blocked
}

Visual feedback is triggered via the dragEnter() function, which highlights the target column by adding an over class:

function dragEnter(columnIndex) {
  listColumns[columnIndex].classList.add("over");
  currentColumn = columnIndex;
}

The corresponding styles in projects/kanban/style.css define the highlight appearance:

.dragndrop-item-list.over {
  background-color: #f0f8ff;   /* light highlight when a column accepts a drop */
}

Executing the Drop Operation

When a user releases a card, the dropItem() function moves the DOM element and synchronizes the underlying data:

function dropItem(e) {
  e.preventDefault();
  listColumns.forEach(col => col.classList.remove("over")); // clear highlights

  const parent = listColumns[currentColumn];
  parent.appendChild(draggedItem);   // move the <li> in the DOM
  isDragging = false;
  rebuildArrays();                  // sync arrays <→ DOM <→ localStorage
}

Persisting Board State with localStorage

The Kanban board maintains four parallel arrays (backlogListArray, progressListArray, completeListArray, onHoldListArray) that mirror the DOM state. This architecture enables reliable data persistence across browser sessions.

Rebuilding Arrays After Movement

After each drop operation, rebuildArrays() reconstructs the column arrays based on the current DOM order. This ensures the JavaScript data structures remain synchronized with the visual layout before saving.

Saving to localStorage

The updateSavedColumns() function persists the arrays to localStorage using JSON serialization:

function updateSavedColumns() {
  listArray = [backlogListArray, progressListArray, completeListArray, onHoldListArray];
  const listNames = ["backlog", "progress", "complete", "onHold"];
  listNames.forEach((name, i) => {
    localStorage.setItem(`${name}Items`, JSON.stringify(listArray[i]));
  });
}

On page initialization, updateDOM() retrieves these values or seeds default arrays, ensuring the board state survives page refreshes.

Adding Interactive Features

Beyond basic drag and drop functionality, the implementation supports inline editing and dynamic card creation.

Inline Card Editing

Each <li> element utilizes the contenteditable attribute, allowing users to modify task text directly. The onfocusout handler triggers updateItem() to propagate changes back to the corresponding array index and immediately persist the update via updateSavedColumns().

Creating New Cards

The board includes a hidden input area toggled by an "Add item" button. When users save new content, addItemToColumn() appends the text to the appropriate column array and refreshes the interface:

function addItemToColumn(columnIndex) {
  const itemText = addItems[columnIndex].textContent;
  const selectedArray = listArray[columnIndex];
  selectedArray.push(itemText);
  addItems[columnIndex].textContent = "";
  updateDOM();
}

Styling the Drag Experience

The CSS in projects/kanban/style.css enhances usability through cursor states and visual transitions:

.drag-item {
  cursor: grab;
}
.drag-item:active {
  cursor: grabbing;
}

The .over class applied during dragEnter provides immediate spatial feedback, helping users identify valid drop targets before releasing the mouse button.

Summary

  • Native API Only: The implementation uses the standard HTML Drag and Drop API (draggable, ondragstart, ondragover, ondrop) without external dependencies.
  • Four-Column Structure: The board manages Backlog, Progress, Complete, and On-Hold columns through parallel arrays and DOM synchronization.
  • Event Flow: drag() records the source element, allowDrop() enables the target zone, dragEnter() highlights the destination, and dropItem() executes the move while triggering persistence.
  • State Persistence: localStorage integration via updateSavedColumns() and rebuildArrays() ensures data survives browser refreshes.
  • File Locations: Core logic resides in projects/kanban/script.js, markup in projects/kanban/index.html, and styling in projects/kanban/style.css.

Frequently Asked Questions

How do I prevent the browser from blocking drop operations?

You must call event.preventDefault() inside the ondragover handler. According to the HTML Drag and Drop API specification, dropping is disabled by default on most elements unless this method explicitly allows the operation, as implemented in the allowDrop() function within projects/kanban/script.js.

What is the purpose of the rebuildArrays() function in the Kanban implementation?

The rebuildArrays() function iterates through the DOM elements in each column after a drop event and reconstructs the four JavaScript arrays (backlogListArray, progressListArray, etc.) to match the current visual order. This synchronization step is essential before calling updateSavedColumns() to persist the correct state to localStorage.

Can I implement this Kanban board without using localStorage?

Yes. While the jisan-mia/dom-projects implementation uses localStorage for persistence, you can remove the updateSavedColumns() calls and maintain state purely in memory using the four column arrays. Alternatively, you could replace the localStorage logic with API calls to a backend server by modifying the persistence layer in projects/kanban/script.js while retaining the same drag-and-drop event handlers.

How does the visual feedback work when dragging cards between columns?

The dragEnter() function applies an over CSS class to the target column's <ul> element when a draggable card enters its boundaries. The stylesheet in projects/kanban/style.css defines .dragndrop-item-list.over with a distinct background color (#f0f8ff), creating immediate visual confirmation of valid drop zones. The dropItem() function removes this class from all columns after the drop completes.

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 →