๐Ÿ“Œ Projects

Build a Drag-and-Drop Kanban Board โ€” HTML5 DnD API

๐Ÿ“… Jul 5, 2026 โฑ 4 min read

A working kanban (Trello-lite) instantly reads as "this person can build real UI". The native DnD API does the heavy lifting.

The mechanics

<div class="column" data-col="todo">
  <h3>To Do</h3>
  <div class="card" draggable="true" id="c1">Finish CN record</div>
</div>
<div class="column" data-col="doing">...</div>
<div class="column" data-col="done">...</div>
// card side
document.addEventListener("dragstart", (e) => {
  if (!e.target.matches(".card")) return;
  e.dataTransfer.setData("text/plain", e.target.id);
  e.target.classList.add("dragging");
});
document.addEventListener("dragend", (e) => e.target.classList?.remove("dragging"));

// column side
document.querySelectorAll(".column").forEach((col) => {
  col.addEventListener("dragover", (e) => {
    e.preventDefault();                    // REQUIRED to allow dropping
    col.classList.add("over");
  });
  col.addEventListener("dragleave", () => col.classList.remove("over"));
  col.addEventListener("drop", (e) => {
    e.preventDefault();
    col.classList.remove("over");
    const card = document.getElementById(e.dataTransfer.getData("text/plain"));
    col.append(card);
    saveState();
  });
});

Persistence

function saveState() {
  const state = {};
  document.querySelectorAll(".column").forEach((col) => {
    state[col.dataset.col] = [...col.querySelectorAll(".card")].map(c => c.textContent);
  });
  localStorage.setItem("kanban", JSON.stringify(state));
}

Add card creation, delete on double-click, and column counts. Mobile note: native DnD ignores touch โ€” mention it honestly or add a pointer-events fallback; knowing the limitation is itself a good interview beat.

โ† All Articles