-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
192: Building a Kanban Board with Drag and Drop
I remember working with a junior dev a few years back who was tasked with building a Trello-style board for our internal project management tool. He spent an entire afternoon fighting with the browser, convinced there was a bug in the Chrome engine. He had the cards moving across the screen, but the second he let go over a column, the card would just snap back to its original position as if nothing had happened. He had the drop event listener perfectly written, but he missed one tiny, unintuitive detail: the dragover event. In the HTML5 Drag and Drop API, the browser's default behavior is to prevent dropping. If you don't explicitly tell the browser to stop that default behavior, your drop event will never even fire.
Building a Kanban board is the perfect way to wrap your head around this because it forces you to manage state between different containers. We aren't just moving a pixel; we're moving a piece of data from one logical state (like "To Do") to another ("Doing").
Capturing the Card's Identity
First, you have to tell the browser which elements are actually movable. You do this by adding the draggable="true" attribute to your card elements. But simply moving the element visually isn't enough; the receiving column needs to know which card is being dropped.
This is where the dataTransfer object comes in. Think of it as a temporary clipboard that follows the mouse cursor during the drag operation. In the dragstart event, we use setData() to store the ID of the card. I usually prefer using a unique id attribute on the card so that the drop target can easily find the element in the DOM.
const card = document.querySelector('.kanban-card');
card.addEventListener('dragstart', (e) => {
// Store the ID of the element being dragged
e.dataTransfer.setData('text/plain', e.target.id);
e.target.classList.add('dragging');
});
card.addEventListener('dragend', (e) => {
e.target.classList.remove('dragging');
});
Making the Columns Receptive
Now we get to the part that tripped up my colleague. To make a column a valid drop zone, you must listen for the dragover event and call event.preventDefault(). If you skip this, the browser will show a "no entry" sign on the cursor, and your drop event will be ignored.
Once the drop event fires, you retrieve the ID we stored earlier using getData(). From there, it's a simple matter of using appendChild(). Since the card already exists in the DOM, appendChild() doesn't create a duplicate; it physically moves the element from the old column to the new one.
const columns = document.querySelectorAll('.kanban-column');
columns.forEach(column => {
column.addEventListener('dragover', (e) => {
// This is the "magic" line that allows the drop to happen
e.preventDefault();
});
column.addEventListener('drop', (e) => {
e.preventDefault();
const cardId = e.dataTransfer.getData('text/plain');
const draggableElement = document.getElementById(cardId);
// Move the card into the current column
column.appendChild(draggableElement);
});
});
One tip from my own experience: don't rely solely on the DOM for your state. While appendChild() handles the visual move, in a real production app, you'd want to trigger an API call inside that drop handler to update the card's status in your database. Otherwise, the second the user refreshes the page, all those carefully organized tasks will jump right back to the "To Do" column.
📋 Practical Task
Build a Sprint Backlog Task Mover
Create a functional Kanban prototype with three columns: "Backlog", "In Progress", and "Done". Each column should start with at least two task cards inside it.
Your implementation must satisfy the following requirements:
- Cards must be draggable using the HTML5 Drag and Drop API.
- The columns must be configured to allow cards to be dropped into them.
- When a card is dropped into a new column, it must physically move to that container.
- Bonus: Add a CSS class called
.draggingthat reduces the opacity of the card while it is being moved to give the user visual feedback.
There are no comments for now.