Build a Kanban Board with JavaScript

Build a drag-and-drop task board with localStorage persistence.

Introduction

30 Second Summary

You know those boards with sticky notes organized into columns like "To Do," "In Progress," and "Done"? That system is called a Kanban board, and it is one of the most popular ways to manage tasks in the world.

In this project, you will build your own Kanban task board from scratch using just HTML, CSS, and JavaScript. You will drag tasks between columns, edit and delete them, and save everything in your browser so it survives page refreshes.

What You'll Build

A fully functional Kanban-style task board that runs entirely in the browser, with drag-and-drop task management, CRUD operations, and persistent data storage using localStorage.

By the end of this project, you'll have:

  • A three-column Kanban board (To Do, In Progress, Done) styled with CSS Flexbox.
  • Drag-and-drop functionality powered by the native HTML5 Drag and Drop API to move task cards between columns.
  • Persistent task data saved in localStorage so your board stays intact even after closing the browser tab.
  • A real-time search bar that filters tasks across all columns as you type (Secret Mission).

Are there any prerequisites?

None. This project walks you through setting up everything you need, including VS Code. All you need is a modern web browser like Chrome, Firefox, Edge, or Safari.

Before We Start

Before We Start...

Set Up VS Code and Create Your Project Files

Before you can build your Kanban board, you need a place to write your code. VS Code is a free code editor that comes with built-in shortcuts to speed up your workflow. In this step, you'll install VS Code and create the three files that make up your entire project.

In This Step, Get Ready To:
  • Install VS Code on your computer.
  • Create your project folder and three core files.
  • Open your HTML file in the browser to confirm everything is connected.
Download and Install VS Code

VS Code is a lightweight code editor made by Microsoft. It works on all major operating systems and includes helpful features like syntax highlighting and Emmet shortcuts out of the box.

  • Open the VS Code download page in your browser.
  • Choose the installer for your operating system (Windows .exe, macOS .dmg, or Linux .deb/.rpm).
  • Run the installer and follow the on-screen prompts.
  • Launch VS Code once the installation is complete.

I see the VS Code welcome screen

You're all set. VS Code is installed and ready to go.

I'm having trouble installing

If the installer doesn't run or you see a permissions error, try running the installer as an administrator (Windows) or dragging the app to your Applications folder (macOS).

Still can't install VS Code?

Help me troubleshoot my VS Code installation.

Create Your Project Folder and Files

Your Kanban board is made up of three files. Each file handles a different layer of the application: structure, styling, and behavior.

  • In VS Code, click File, then Open Folder.
  • Navigate to a location on your computer where you want to store this project (like your Desktop or Documents folder).
  • Create a new folder called kanban-board and open it.

You should now see an empty folder open in VS Code's sidebar. Time to create your three files.

  • Hover over the KANBAN-BOARD heading in the sidebar and click the New File icon.
  • Type index.html and press Enter.
  • Repeat this process to create two more files: styles.css and app.js.

What are these three files for?

  • index.html defines the structure of your board (headings, columns, buttons).
  • styles.css controls how everything looks (colors, layout, animations).
  • app.js handles all the behavior (creating tasks, drag-and-drop, saving data).
Set Up the HTML Boilerplate and Link Your Files

Every HTML page needs a standard boilerplate structure. VS Code has a built-in shortcut called Emmet that generates this for you instantly.

  • Click on index.html in the sidebar to open it.
  • Type ! and press Tab.

VS Code generates a full HTML5 boilerplate for you. Now you need to connect your CSS and JavaScript files to this HTML page.

  • Replace the contents of index.html with the following code.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Kanban Board</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>

    <script src="app.js"></script>
</body>
</html>

What do these two lines do?

The <link rel="stylesheet" href="styles.css"> tag in the <head> tells the browser to load your CSS file for styling.

The <script src="app.js"></script> tag before the closing </body> loads your JavaScript file. Placing it at the bottom ensures the HTML content loads first before any scripts run.

Now open this file in your browser to confirm it loads.

  • Open your computer's file explorer and navigate to your kanban-board folder.
  • Right-click index.html and choose Open with your preferred browser (Chrome, Firefox, Edge, or Safari).

What should I see?

You should see a blank white page with the tab title showing "Kanban Board". That's exactly right. The page is empty because you haven't added any visible content to the <body> yet. You'll do that in the next step.

Your project files are all set up and connected. Next up, you'll build the actual board layout with three columns using HTML.

Build the Board Layout with HTML

Your project files are ready and linked together. Now it's time to build the actual structure of your Kanban board.

Every web page starts with HTML, which defines the structure and content of what appears on screen. Think of HTML as the skeleton of your board. You'll create a header, three columns, and buttons. It won't look pretty yet, but the structure is what everything else builds on.

In This Step, Get Ready To:
  • Create a header and board container for your Kanban layout.
  • Build three columns (To Do, In Progress, Done) with task list areas and add buttons.
  • Preview the raw HTML structure in your browser.
Create the Header and Board Container

Your board needs two top-level sections: a header to display the title, and a main container that holds all three columns. The container will use a class called board, which you'll target with CSS Flexbox in the next step to lay out the columns side by side.

  • Open index.html in VS Code.
  • Inside the <body> tag (above the <script> tag), add a <header> element containing an <h1> with the text "Kanban Board".
  • Below the header, add a <div> with a class of board. This container will hold all three columns.

Inside the board div, create three column <div> elements. Each column needs a class of column and a data-status attribute. The data-status attribute is a custom HTML attribute that your JavaScript will use later to know which column a task belongs to.

  • Add the first column <div> with data-status="todo".
  • Add the second column <div> with data-status="in-progress".
  • Add the third column <div> with data-status="done".

Your code should look like this so far:

<header>
  <h1>Kanban Board</h1>
</header>

<div class="board">
  <div class="column" data-status="todo">
  </div>

  <div class="column" data-status="in-progress">
  </div>

  <div class="column" data-status="done">
  </div>
</div>

What is a data attribute?

HTML data-* attributes let you store custom information directly on an element. Your JavaScript can read these values later to figure out which column is which. For example, data-status="todo" tells your code that this column holds tasks with a status of todo.

Add Column Content and Add-Task Buttons

Each column needs three things inside it: a heading that labels the column, a container where task cards will appear, and a button for adding new tasks.

  • Inside the first column (todo), add an <h2> with the text "To Do".
  • Below the heading, add a <div> with a class of task-list. This is where task cards will be inserted by JavaScript later.
  • Below the task list div, add a <button> with a class of add-task-btn and the text "+ Add Task".
  • Repeat this same structure inside the second column (in-progress) with the heading "In Progress".
  • Repeat again inside the third column (done) with the heading "Done".

Here is the complete HTML for all three columns:

<header>
  <h1>Kanban Board</h1>
</header>

<div class="board">
  <div class="column" data-status="todo">
    <h2>To Do</h2>
    <div class="task-list"></div>
    <button class="add-task-btn">+ Add Task</button>
  </div>

  <div class="column" data-status="in-progress">
    <h2>In Progress</h2>
    <div class="task-list"></div>
    <button class="add-task-btn">+ Add Task</button>
  </div>

  <div class="column" data-status="done">
    <h2>Done</h2>
    <div class="task-list"></div>
    <button class="add-task-btn">+ Add Task</button>
  </div>
</div>

Why are the task-list divs empty?

The task-list containers are intentionally empty. Your JavaScript code will create and insert task cards into these divs dynamically. This is a common pattern in web development: HTML provides the structure, and JavaScript fills in the content.

Preview the Raw HTML in Your Browser

Time to see your work in action. The page won't look polished yet because you haven't added any CSS styles. That's completely expected.

  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Open the file in your browser by navigating to your file explorer, finding index.html, and double-clicking it. Or refresh the page if you already have it open.

What should I see?

You should see a plain page with "Kanban Board" as a large heading at the top. Below it, the three column headings (To Do, In Progress, Done) appear stacked vertically, each followed by a "+ Add Task" button. Everything is unstyled and left-aligned. This is normal. You'll transform this into a polished layout in the next step with CSS.

Your board's skeleton is complete. Next up, you'll use CSS to transform these plain elements into a sleek, side-by-side column layout.

Style the Board with CSS

Your HTML structure is in place, but right now it looks like a plain list of text. A Kanban board needs its three columns displayed side by side so you can visually track tasks moving from left to right. In this step, you'll use CSS Flexbox to transform your raw HTML into a polished, interactive-looking board.

In This Step, Get Ready To:
  • Set up a CSS reset and background color for the page.
  • Lay out three equal-width columns side by side using Flexbox.
  • Create styles for task cards with hover effects and drag feedback.
Reset Defaults and Set Up the Board Layout

Browsers apply default margins and padding to HTML elements, which creates inconsistent spacing. A CSS reset removes those defaults so you start from a clean slate. You'll also use display: flex on the board container to place the three columns next to each other.

  • Open styles.css in VS Code.
  • Add the following CSS to reset defaults and lay out the board.
/* Reset browser defaults */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background-color: #f0f2f5;
  padding: 20px;
}

/* Header styling */
h1 {
  text-align: center;
  margin-bottom: 30px;
  color: #333;
}

/* Flexbox layout for the three columns */
.board {
  display: flex;
  gap: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

What Does This Code Do?

The * selector targets every element on the page, zeroing out margins and padding. The box-sizing: border-box rule makes padding count inside an element's width instead of adding to it, which prevents layout surprises.

The display: flex on .board turns the container into a flex container. Its children (the three columns) automatically line up in a row. The gap: 20px adds consistent spacing between columns without needing margins on each one.

Style the Columns and Add Buttons

Each column needs to share the available width equally. You also need to give the task list area inside each column a minimum height. Without it, empty columns would collapse to nothing, leaving no visible area for dropping tasks later.

  • Add the following styles below your existing CSS in styles.css.
/* Column styling */
.column {
  flex: 1;
  background-color: #e2e6ea;
  border-radius: 10px;
  padding: 15px;
}

.column h2 {
  font-size: 1.1rem;
  color: #555;
  margin-bottom: 15px;
  text-transform: uppercase;
  letter-spacing: 1px;
}

/* Drop zone for task cards */
.task-list {
  min-height: 200px;
  margin-bottom: 10px;
}

/* Add task button */
.add-btn {
  width: 100%;
  padding: 10px;
  background-color: transparent;
  border: 2px dashed #bbb;
  border-radius: 8px;
  color: #888;
  font-size: 1.2rem;
  cursor: pointer;
  transition: background-color 0.2s, border-color 0.2s;
}

.add-btn:hover {
  background-color: #d5d9dd;
  border-color: #999;
}

What Does flex: 1 Do?

The flex: 1 property tells each column to grow and fill an equal share of the available space. Since all three columns have the same value, they each take up one-third of the board's width.

  • Save the file and refresh your browser.

You should now see three rounded columns sitting side by side, each with a heading and a dashed "+" button at the bottom. Hover over the button to see the color transition.

Create Task Card and Drag Feedback Styles

You haven't created any task cards in JavaScript yet, but you can prepare the styles now. When you build the JavaScript in the next step, the cards will immediately look polished. You'll also create a .dragging class that JavaScript will toggle during drag operations to give users visual feedback.

  • Add the following styles at the bottom of styles.css.
/* Task card styling */
.task-card {
  background-color: #fff;
  padding: 12px 15px;
  border-radius: 8px;
  margin-bottom: 10px;
  cursor: grab;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s, box-shadow 0.2s;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.task-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

/* Visual feedback while dragging */
.task-card.dragging {
  opacity: 0.3;
  cursor: grabbing;
}

/* Highlight column when a card is dragged over it */
.task-list.drag-over {
  background-color: #d0d4d8;
  border-radius: 8px;
  transition: background-color 0.2s;
}

/* Task action buttons */
.task-actions button {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.9rem;
  margin-left: 8px;
  color: #888;
}

.task-actions button:hover {
  color: #333;
}

What Do These Styles Do?

The .task-card styles give each card a white background with a subtle shadow. The cursor: grab tells users the card is draggable. On hover, transform: translateY(-2px) lifts the card slightly and the shadow deepens, creating a "picked up" feel.

The .dragging class drops the card's opacity to 0.3, making it nearly transparent while it's being dragged. The .drag-over class highlights the target column so users can see exactly where they're about to drop a card.

Pro tip: preview cards now

Want to see what a task card looks like before writing JavaScript? Temporarily add a <div class="task-card">Sample Task</div> inside one of your .task-list containers in index.html. Refresh the browser to see the card styling, then remove it before moving on.

Your board now looks like a real Kanban app. Next up, you'll bring it to life by writing JavaScript that creates task cards, saves them to localStorage, and renders them on the board.

Create and Display Tasks with JavaScript

Your board looks great, but it is completely static. You need a way to create tasks, store them as data, and render them on the page dynamically. In this step, you will write the JavaScript that powers your board's task system, including saving tasks to localStorage so they survive page refreshes.

In This Step, Get Ready To:
  • Define a task data model and build save/load functions with localStorage.
  • Write a render function that dynamically creates task cards in the correct columns.
  • Wire up the "+" buttons so you can add new tasks and see them persist after a refresh.
Define the Task Data Model and Storage Functions

Every task on your board needs a consistent shape so your code knows what to expect. You will represent each task as a JavaScript object with three properties: a unique ID, a title, and a status that matches one of your three columns.

  • Open app.js in VS Code.
  • At the top of the file, add the following code to set up your task array and storage functions.
// Task array - this holds all tasks in memory
let tasks = [];

// Save tasks to localStorage so they persist across page refreshes
function saveTasks() {
  localStorage.setItem('kanbanTasks', JSON.stringify(tasks));
}

// Load tasks from localStorage when the page opens
function loadTasks() {
  const stored = localStorage.getItem('kanbanTasks');
  tasks = stored ? JSON.parse(stored) : [];
}

What Does This Code Do?

The tasks array is your single source of truth. Every task you create gets added here as an object.

The saveTasks() function converts the array to a JSON string and stores it in localStorage. The loadTasks() function reads that string back and parses it into a JavaScript array. If nothing is stored yet, it defaults to an empty array.

What is localStorage?

localStorage is a built-in browser feature that lets you store key-value pairs of text data. It has a size limit of approximately 5MB per website, which is more than enough for a task board. Unlike cookies, localStorage data never expires on its own. It stays in your browser until you or your code explicitly removes it.

Each task object follows this shape:

// Example task object (you don't need to add this to your file)
{
  id: '1718304000000',   // unique ID from Date.now()
  title: 'Buy groceries', // the task name
  status: 'todo'          // matches a column: 'todo', 'in-progress', or 'done'
}

Pro tip: Date.now() for IDs

Date.now() returns the number of milliseconds since January 1, 1970. Since you are the only user and tasks are created one at a time, this value is unique enough to serve as an ID for each task.

Write the Render Function

The render function is the heart of your board. Every time something changes (a task is added, moved, or deleted), you will call this function to rebuild the entire display from the tasks array. This pattern keeps your data and your UI in sync.

  • Add the following renderTasks() function below your storage functions in app.js.
function renderTasks() {
  // Clear all task lists before re-rendering
  document.querySelectorAll('.task-list').forEach(list => {
    list.innerHTML = '';
  });

  // Loop through every task and create a card for it
  tasks.forEach(task => {
    // Create the card element
    const card = document.createElement('div');
    card.className = 'task-card';
    card.setAttribute('draggable', 'true');
    card.dataset.id = task.id;

    // Build the card's inner HTML with title and action buttons
    card.innerHTML = `
      <span class="task-title">${task.title}</span>
      <div class="task-actions">
        <button class="edit-btn">Edit</button>
        <button class="delete-btn">Delete</button>
      </div>
    `;

    // Find the matching column and append the card
    const column = document.querySelector(`.column[data-status="${task.status}"] .task-list`);
    if (column) {
      column.appendChild(card);
    }
  });
}

What Does This Function Do?

First, it clears every .task-list container so you start fresh. Then it loops through the tasks array and creates a new <div> for each task. Each card gets the task-card class (so your CSS styles apply), draggable="true" (for drag-and-drop in the next step), and a data-id attribute so you can identify which task it represents.

The card is placed into the correct column by matching the task's status property to the column's data-status attribute.

Wire Up the Add Buttons and Initialize the Board

Your data model and render function are ready. Now you need to connect the "+" buttons in each column so clicking them creates a new task. You also need to load any saved tasks and render them when the page first opens.

  • Add the following code at the bottom of app.js.
// Add click listeners to every "+" button in each column
document.querySelectorAll('.column .add-btn').forEach(button => {
  button.addEventListener('click', () => {
    // Get the column's status from its parent element
    const status = button.closest('.column').dataset.status;

    // Prompt the user for a task title
    const title = prompt('Enter task title:');

    // Only create the task if the user typed something
    if (title && title.trim()) {
      const newTask = {
        id: Date.now().toString(),
        title: title.trim(),
        status: status
      };

      tasks.push(newTask);
      saveTasks();
      renderTasks();
    }
  });
});

// Initialize: load saved tasks and render them
loadTasks();
renderTasks();

What Does This Code Do?

The first block finds every "+" button and attaches a click listener. When clicked, it figures out which column the button belongs to using button.closest('.column').dataset.status. It then prompts the user for a title, creates a new task object, adds it to the array, saves to localStorage, and re-renders the board.

The last two lines run when the page first loads. They pull any previously saved tasks out of localStorage and display them on the board.

Time to test your work.

  • Save app.js and refresh your browser.
  • Click the "+" button in the To Do column.
  • Type a task name like Buy groceries and click OK.
  • Add two or three more tasks across different columns.
  • Refresh the page to confirm your tasks are still there.

What Should I See?

Each task should appear as a styled card with its title, an Edit button, and a Delete button. After refreshing the page, all your tasks should still be visible in the same columns. The Edit and Delete buttons will not work yet. You will wire those up in the next step.

Tasks Disappearing After Refresh?

Make sure your loadTasks() and renderTasks() calls are at the very bottom of app.js. Also check that your <script> tag in index.html is placed just before the closing </body> tag so the DOM is fully loaded before your script runs. Still stuck? Ask for help here.

Your board can now create and display tasks that persist across page refreshes. Next up, you will make those cards draggable so you can move them between columns.

Add Drag-and-Drop Between Columns

Your tasks show up on the board and survive page refreshes. Now it is time to make them movable. The whole point of a Kanban board is dragging tasks from one column to another as work progresses. In this step, you will wire up the HTML5 Drag and Drop API so cards can be picked up and dropped into any column.

In This Step, Get Ready To:
  • Add drag event listeners to each task card so it can be picked up.
  • Add drop zone listeners to each column so it can receive cards.
  • Verify that dragged tasks persist in their new column after a page refresh.
Make Task Cards Draggable

The HTML5 Drag and Drop API works through a series of events. When a user picks up a card, a dragstart event fires. When they release it, a dragend event fires. You need to tell the browser which piece of data is being dragged and provide visual feedback while the drag is happening.

  • Open app.js and find your renderTasks() function.
  • Inside the loop, right after you create each task card element and set draggable="true", add the following event listeners:
// Drag start - store the task ID and show visual feedback
card.addEventListener('dragstart', (e) => {
  e.dataTransfer.setData('text/plain', task.id);
  e.dataTransfer.effectAllowed = 'move';
  card.classList.add('dragging');
});

// Drag end - remove visual feedback
card.addEventListener('dragend', () => {
  card.classList.remove('dragging');
});

What does this code do?

When the user starts dragging a card, setData() stores the task's ID in the drag event. This is how the drop zone will know which task was dragged. The effectAllowed = 'move' property tells the browser this is a move operation, not a copy.

Adding the dragging CSS class makes the card semi-transparent while it is being dragged. The dragend listener removes that class when the drag finishes, whether the drop succeeds or not.

Set Up Drop Zones on Each Column

By default, most HTML elements do not accept drops. You need to explicitly allow dropping by handling the dragover and drop events on each column's task list container.

  • Below your renderTasks() function, select all .task-list containers and add dragover and drop event listeners to each one.
  • Add the following code to app.js:
// Set up drop zones
const taskLists = document.querySelectorAll('.task-list');

taskLists.forEach(taskList => {
  // Allow dropping by preventing the default behavior
  taskList.addEventListener('dragover', (e) => {
    e.preventDefault();
    taskList.classList.add('drag-over');
  });

  // Handle the drop
  taskList.addEventListener('drop', (e) => {
    e.preventDefault();
    taskList.classList.remove('drag-over');

    // Get the dragged task's ID
    const taskId = e.dataTransfer.getData('text/plain');

    // Find the task in the array
    const task = tasks.find(t => t.id == taskId);

    // Update the task's status to match the column it was dropped into
    const newStatus = taskList.closest('.column').dataset.status;
    task.status = newStatus;

    saveTasks();
    renderTasks();
  });
});

Why is event.preventDefault() required?

Browsers block drops on most elements by default. Calling event.preventDefault() in the dragover handler signals that this element is a valid drop target. Without it, the drop event will never fire.

How does the drop know which column it landed in?

The line taskList.closest('.column').dataset.status walks up the DOM from the task list to its parent column element. It then reads the data-status attribute you set in your HTML (todo, in-progress, or done). That value becomes the task's new status.

  • Open styles.css and add a style for the .drag-over class so columns light up when a card hovers over them:
.drag-over {
  background-color: rgba(0, 0, 0, 0.05);
  border: 2px dashed #999;
  border-radius: 8px;
}

What does this style do?

This gives the user a visual hint that the column is ready to accept a drop. The dashed border and subtle background change make it clear where the card will land.

Clean Up the Drag Highlight and Test

There is one loose end. If a user drags a card over a column but then moves away without dropping, the highlight stays visible. The dragleave event handles this cleanup.

  • Add a dragleave event listener inside the same taskLists.forEach loop, right after the drop listener:
  // Remove highlight when dragging leaves the column
  taskList.addEventListener('dragleave', () => {
    taskList.classList.remove('drag-over');
  });

What does this listener do?

When the dragged card leaves a column without being dropped, this removes the drag-over class so the dashed border and background disappear. This keeps the visual feedback accurate.

Now it is time to test the full drag-and-drop flow.

  • Save all your files and refresh the browser.
  • Add a few tasks to the To Do column if you do not have any.
  • Click and hold a task card, then drag it to the In Progress column.
  • Release the card and confirm it appears in the new column.
  • Refresh the page to verify the task stays in its new column.

What should I see?

When you start dragging, the card should become semi-transparent (from the dragging CSS class). When you hover over another column, a dashed border should appear. After dropping, the card should snap into the new column. After refreshing, the card should still be in the column you dropped it into.

Card not dropping into the column?

The most common cause is a missing event.preventDefault() in your dragover handler. Without it, the browser will not allow the drop. Double-check that both your dragover and drop handlers call e.preventDefault(). Also confirm that your drop zone listeners are attached to the .task-list elements, not the .column elements. Help me debug my drag-and-drop code.

Your board is fully interactive now. Tasks can be created and dragged between columns, and every change persists in localStorage. Next up, you will add the ability to edit and delete tasks so your board is a complete task management tool.

Edit and Delete Tasks

Your board already lets you create tasks and drag them between columns. Now it's time to complete the CRUD cycle by adding the ability to update and remove tasks. In this step, you'll wire up the edit and delete buttons so users have full control over their task cards.

In This Step, Get Ready To:
  • Add edit functionality that lets users rename existing tasks.
  • Add delete functionality with a confirmation prompt.
  • Test the full workflow of creating, moving, editing, deleting, and persisting tasks.
Add Edit Functionality to Task Cards

Each task card already has an edit button from the renderTasks() function you built earlier. Now you need to make that button actually do something. When clicked, it should let the user change the task's title and save the update to localStorage.

  • Open app.js and find the section inside renderTasks() where you create the edit button for each task card.
  • Add a click event listener to the edit button with the following logic:
editBtn.addEventListener('click', () => {
  const newTitle = prompt('Edit task title:', task.title);
  if (newTitle !== null && newTitle.trim() !== '') {
    task.title = newTitle.trim();
    saveTasks();
    renderTasks();
  }
});

What does this code do?

The prompt() function opens a browser dialog pre-filled with the task's current title. The user can modify the text and click OK, or click Cancel to abort. The code checks that the user didn't cancel (newTitle !== null) and didn't leave the field blank before updating the task object, saving to localStorage, and re-rendering the board.

Add Delete Functionality to Task Cards

Deleting a task is destructive, so it's good practice to ask for confirmation before removing anything. You'll use the browser's built-in confirm() dialog for this.

  • Find the section inside renderTasks() where you create the delete button for each task card.
  • Add a click event listener to the delete button with the following logic:
deleteBtn.addEventListener('click', () => {
  if (confirm('Are you sure you want to delete this task?')) {
    tasks = tasks.filter(t => t.id !== task.id);
    saveTasks();
    renderTasks();
  }
});

What does this code do?

The confirm() function shows a dialog with OK and Cancel buttons. If the user clicks OK, the code uses Array.filter() to create a new array that excludes the task with the matching id. This new array replaces the old tasks array, and the board re-renders without the deleted card.

Pro tip: make sure tasks is reassignable

Since the delete logic reassigns the tasks variable with tasks = tasks.filter(...), make sure you declared tasks with let at the top of your file, not const. Using const would throw an error because you can't reassign a constant.

Test the Full Workflow

With edit and delete in place, your Kanban board now supports the full CRUD lifecycle. Time to put it through its paces.

  • Refresh your browser to load the latest code.
  • Create three or four new tasks using the "+" buttons in different columns.
  • Drag a task from the **To Do** column to the **In Progress** column.
  • Click the edit button on one of your tasks and change its title.
  • Click the delete button on another task and confirm the deletion.
  • Refresh the page to verify that all your changes persisted.

After refreshing, your edited task should show its new title, your deleted task should be gone, and your dragged task should still be in the column you moved it to.

Edit or delete not working?

Open your browser's developer console by pressing F12 and check for any error messages. Common issues include the event listeners being attached outside the renderTasks() function, or the tasks variable being declared with const instead of let. Help me debug my edit and delete buttons.

Your Kanban board is fully functional. You can create, read, update, and delete tasks, drag them between columns, and everything persists across page refreshes. Next up, take on the secret mission to add real-time search filtering to your board.

Secret mission

Add a Search Bar That Filters Tasks in Real Time

Your Kanban board works great, but what happens when you have dozens of tasks? Scrolling through every column to find one task is not ideal. In this secret mission, you will add a search bar that instantly filters tasks across all columns as you type.

Before you go

Clean Up Your Resources

This project runs entirely locally in your browser and VS Code, so there are no ongoing costs. Decide whether to keep your project files, or delete them entirely.

Resources you used:

  • The kanban-board project folder containing index.html, styles.css, and app.js.
  • Task data stored in your browser's localStorage under the key kanbanTasks.
  • VS Code (if you installed it for this project).

Keep everything running

No action needed. Choose this if you are still actively building or want to keep using your Kanban board day-to-day.

  • Your project files stay in the kanban-board folder.
  • Your task data remains in localStorage and loads automatically whenever you open index.html.
  • There are no costs associated with keeping these files.

Pause - I'll come back to this later

Since this project is entirely local, there is nothing to pause. Your files and localStorage data will stay where they are until you come back.

  • Keep the kanban-board folder on your computer for when you are ready to continue.
  • Your task data in localStorage persists automatically. Just reopen index.html in your browser to pick up where you left off.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete the kanban-board folder from your computer.
  • Clear your localStorage data by opening your browser's Developer Tools (F12), navigating to the Application tab, selecting Local Storage, and deleting the kanbanTasks entry.
  • If you installed VS Code specifically for this project and no longer need it, uninstall it from your system.

Mission accomplished

That's a Wrap!

Nice work! You just built a fully functional Kanban task board from scratch using nothing but HTML, CSS, and JavaScript.

Here's what you learned:

  • Built a three-column Kanban board layout with HTML and CSS Flexbox, and styled task cards with hover effects and drag animations.
  • Implemented the HTML5 Drag and Drop API to move tasks between columns using native browser events like dragstart, dragover, and drop.
  • Built a complete CRUD system for tasks and persisted all data in localStorage so your board survives page refreshes.
  • Added a real-time search bar that filters tasks across all columns as you type.

Ready to quiz yourself?