Kanban Task Board
Build a Kanban board with drag-and-drop cards, statuses, and saved progress
Time to implement the project: ~ 18-28 hours
- HTML
- CSS
- Layouts
- JavaScript
- Drag and Drop
- Local Storage
- State Management
In this project, you will build a Kanban-style task board with multiple columns (for example: To Do, In Progress, Done) and draggable task cards that move between columns. Each card represents a task with a title and optional details. Your board must support creating new tasks, placing them into a starting column, and updating a card’s status by dragging it into another column.
The key requirement is persistence: tasks and their column positions must be saved between sessions using browser storage. When a user refreshes the page or returns later, the board should restore exactly as it was. Your implementation should keep the UI responsive, prevent broken drop zones, and make interactions obvious through clear hover/focus states and drop feedback.
Project Outcome and Practical Value
This project teaches the fundamentals of interactive stateful interfaces: you will manage a set of tasks, render them into columns, and keep the UI in sync with the underlying data model. Drag-and-drop forces you to think in terms of events, identifiers, and state transitions rather than static markup.
A Kanban board mirrors real product UI patterns used in workplace tools. Completing it builds credibility in interviews because it demonstrates interaction design, data persistence, and clean DOM updates - skills that are considered core for junior frontend developers.
What You Need Before You Start
You should feel confident with basic layouts and JavaScript-driven UI updates. The project expects you to structure data, respond to user actions, and store state in the browser without external libraries.
- Comfortable building multi-column layouts with CSS3
- Understanding of DOM selection, events, and updates
- Working knowledge of HTML5 Drag and Drop or pointer-based alternatives
- Ability to serialize and parse data using JSON
- Familiarity with browser storage concepts (localStorage)
- Basic debugging in DevTools (console, elements, storage tabs)
Core Requirements for a Working Kanban Board
A strong solution behaves reliably under real usage: cards move where users expect, the board communicates valid drop zones, and the state survives refreshes without data corruption. These requirements reflect typical expectations for interactive UI exercises and help you practice production-style thinking.
| Requirement | Explanation |
| Three or more status columns | Columns establish the workflow model and make status transitions explicit in the UI. |
| Create and render task cards | Card creation proves you can transform user input into data and UI consistently. |
| Drag start, drag over, and drop handling | Proper event handling prevents broken drops and supports predictable movement between columns. |
| Drop zone feedback | Visual cues reduce user mistakes and make the interaction feel responsive and intentional. |
| Status updates tied to data, not DOM only | Data-driven status prevents UI from desyncing and simplifies persistence and re-rendering. |
| Persistent storage between sessions | Saving tasks to localStorage ensures the board restores after refresh and builds real app habits. |
| Stable identifiers for tasks | Unique IDs prevent collisions, enable reliable moves, and support future features like editing. |
| Graceful handling of empty columns | Empty states keep the UI usable and prevent layout collapse when tasks move out. |
Tips to Build It Like a Real UI
Start with the data model before you polish the UI. Define how tasks are stored (id, title, status, order) and treat the DOM as a rendering layer. Implement create, move, and persist operations one by one, verifying storage on every change. When drag-and-drop feels inconsistent, inspect event targets and keep drop handlers attached to column containers, not individual cards. When the data stays correct, the UI becomes easy to fix.
- Store tasks as an array of objects and re-render columns from state after every change
- Use a single source of truth for status and persist immediately after drop completes
- Add clear empty states so users understand where cards can be dropped
- Prevent accidental text selection and jitter by styling draggable elements intentionally
- Validate storage data on load to avoid breaking the board when JSON becomes corrupted
Common Kanban Board Mistakes and How to Fix Them
1. Moving cards only in the DOM and forgetting to update the data state
The most common mistake in Kanban projects is treating the DOM as the main source of truth. A beginner may drag a card into another column visually, but forget to update the underlying task object. The board looks correct until the page refreshes, filtering is added, or localStorage restores the old state.
Problematic approach:
column.appendChild(draggedCard);
This only moves the HTML element. It does not update the task status, task order, or saved data.
Better approach:
function moveTask(taskId, newStatus) {
tasks = tasks.map((task) => {
if (task.id !== taskId) return task;
return {
...task,
status: newStatus
};
});
saveTasks();
renderBoard();
}
Pay attention to: The board should always be rendered from data. Drag-and-drop should update the task object first, then re-render the columns from the updated state. This prevents UI and storage from becoming different versions of the truth.
2. Using task titles as identifiers
Many beginners use the task title to find and move a card. This works only until two tasks have the same name. Real task boards often contain repeated titles such as “Fix bug”, “Update copy”, or “Review design”, so titles should never be used as unique identifiers.
Problematic code:
function findTaskByTitle(title) {
return tasks.find((task) => task.title === title);
}
If two cards have the same title, the wrong task may be moved, edited, or deleted.
Recommended solution:
function createTask(title) {
return {
id: crypto.randomUUID(),
title,
status: 'todo',
createdAt: Date.now()
};
}
Then use the id during drag:
card.setAttribute('draggable', 'true');
card.dataset.taskId = task.id;
card.addEventListener('dragstart', (event) => {
event.dataTransfer.setData('text/plain', task.id);
});
Pay attention to: Every task must have a stable unique id. Use that id for moving, editing, deleting, saving, and restoring cards. This is one of the most important habits in stateful UI development.
3. Forgetting dragover preventDefault on drop zones
In the HTML Drag and Drop API, a drop event will not work correctly unless the target allows dropping. Beginners often add a drop listener and wonder why
nothing happens. The missing part is usually event.preventDefault() inside the dragover handler.
Incomplete implementation:
column.addEventListener('drop', (event) => {
const taskId = event.dataTransfer.getData('text/plain');
moveTask(taskId, column.dataset.status);
});
The browser may block the drop because the column has not been marked as a valid drop target.
Correct implementation:
column.addEventListener('dragover', (event) => {
event.preventDefault();
column.classList.add('is-drag-over');
});
column.addEventListener('dragleave', () => {
column.classList.remove('is-drag-over');
});
column.addEventListener('drop', (event) => {
event.preventDefault();
const taskId = event.dataTransfer.getData('text/plain');
const newStatus = column.dataset.status;
column.classList.remove('is-drag-over');
moveTask(taskId, newStatus);
});
Pay attention to: Attach drop logic to the column container, not only to individual cards. Empty columns must also accept drops, otherwise users cannot move tasks into an empty workflow stage.
4. Saving to localStorage without validating restored data
localStorage is useful, but it should not be trusted blindly. Data can become corrupted, manually edited, outdated after a code change, or missing required fields. If your app assumes localStorage always contains valid JSON, one broken value can crash the entire board.
Risky approach:
const tasks = JSON.parse(localStorage.getItem('kanbanTasks'));
This will throw an error if the stored value is invalid JSON. It may also return null or an unexpected data shape.
Safer approach:
function loadTasks() {
try {
const saved = JSON.parse(localStorage.getItem('kanbanTasks'));
if (!Array.isArray(saved)) {
return [];
}
return saved.filter((task) => {
return task.id && task.title && task.status;
});
} catch (error) {
console.warn('Saved board data is invalid. Resetting tasks.', error);
return [];
}
}
Saving state:
function saveTasks() {
localStorage.setItem('kanbanTasks', JSON.stringify(tasks));
}
Pay attention to: Always protect your app from invalid storage data. A professional UI should recover gracefully instead of showing a blank page because one stored value became unreadable.
5. Losing task order after every render
A Kanban board is not only about status columns. Order matters too. If users rearrange cards inside a column but your data only stores status, the order
may reset after refresh or re-render. This makes the board feel unreliable.
Incomplete data model:
{
id: 'task-1',
title: 'Update homepage',
status: 'in-progress'
}
This tells you where the task belongs, but not where it should appear inside that column.
Better data model:
{
id: 'task-1',
title: 'Update homepage',
status: 'in-progress',
order: 2
}
Render with sorting:
const columnTasks = tasks
.filter((task) => task.status === status)
.sort((a, b) => a.order - b.order);
Pay attention to: If your project supports reordering, store order in the data model. Even if you begin with simple movement between columns, planning for order early prevents painful refactoring later.
6. Creating drop feedback that does not match real drop behavior
Visual feedback must match what the board can actually do. Beginners sometimes highlight a card or column on hover, but the item cannot really be dropped there because the event listener is attached somewhere else. This creates a frustrating interaction where the UI promises an action that does not work.
Confusing CSS-only feedback:
.column:hover {
border-color: #2fc969;
}
This highlights the column for normal hover, not necessarily for a valid drag state.
Better drag-specific feedback:
.column.is-drag-over {
border-color: #2fc969;
background: rgba(47, 201, 105, 0.08);
}
.task-card.is-dragging {
opacity: 0.5;
}
JavaScript state:
card.addEventListener('dragstart', () => {
card.classList.add('is-dragging');
});
card.addEventListener('dragend', () => {
card.classList.remove('is-dragging');
});
column.addEventListener('dragover', (event) => {
event.preventDefault();
column.classList.add('is-drag-over');
});
Pay attention to: Use drag-specific classes such as is-dragging and is-drag-over. This makes visual feedback honest, easier
to debug, and directly connected to real interaction state.
By completing this project, you'll learn how to build an interactive task board with reliable drag-and-drop behavior, clear status transitions, and persistent storage that restores the UI across sessions. This foundation strengthens your understanding of state-driven rendering, DOM event design, and practical frontend architecture, preparing you for larger applications such as dashboards, admin panels, and workflow tools used in real pro
Reference Kanban Board Implementations
Beginner-friendly Kanban implementation:
SMirjanic23 - Kanban Board
This repository is useful as a starting reference because it focuses on the core idea of a Kanban board: organizing tasks into workflow columns and moving cards through different stages. It can help beginners understand how columns, cards, and task status are represented in a real project structure rather than only as isolated HTML blocks.
What to study in the code:
- How task cards are represented in the markup and data.
- How the board separates workflow columns visually.
- How card movement is handled between statuses.
- Whether the project updates data state or only moves DOM elements.
- How the implementation could be extended with localStorage persistence.
Use this repository as a baseline reference. After reviewing it, compare your own project against the same core requirements: stable task ids, clear columns, predictable movement, and saved board state.
Structured board example:
KalMarek7 - Kanban Board
This implementation is useful for studying how a Kanban project can be organized when the board becomes more than a static layout. It gives learners another perspective on how to structure card data, UI sections, and user interactions while keeping the board readable and maintainable.
Pay attention to:
- How the project organizes files and separates responsibilities.
- How task creation or task rendering is handled.
- How drag-and-drop behavior is connected to board state.
- How the layout handles multiple columns and different task counts.
- How easy it would be to add editing, deleting, filtering, or sorting features.
This is a good reference for improving your own architecture. Do not only look at the final UI; inspect whether the code would remain easy to maintain after adding more board features.
Alternative Kanban board approach:
hasanulmukit - Kanban Board
This repository provides an alternative implementation that is useful for comparison. Studying more than one Kanban board helps you see that the same product idea can be built with different data models, layout decisions, and interaction strategies. That comparison is valuable because Kanban projects often become difficult when state and UI are too tightly coupled.
What to compare with your own project:
- How columns and tasks are modeled internally.
- How the implementation handles moving cards between columns.
- Whether empty columns remain valid drop zones.
- How visual feedback communicates dragging and dropping.
- How persistence, editing, or task ordering could be added cleanly.
A useful exercise is to open this repository next to your own solution and identify three improvements: better task ids, cleaner drop handling, stronger empty states, or more reliable storage restoration.