Building an Advanced To-Do List with Drag & Drop and Local Storage

A to-do list is one of the most common beginner projects in web development, but in this blog post, we’ll take it to the next level! 🚀 We’ll build an advanced to-do list using HTML, CSS, and JavaScript, featuring:

Drag & Drop functionality
Local Storage support (Tasks persist even after refreshing)
Dynamic Task Management

This guide will walk you through the code and explain how everything works step by step.


🔹 Features of This To-Do List

  • Users can add new tasks to the list.
  • Tasks are stored in local storage, so they don’t disappear when the page is reloaded.
  • Tasks can be rearranged via drag & drop.
  • The UI is clean and simple, built with vanilla JavaScript.

🛠️ Full Code for the Advanced To-Do List

Here’s the complete code for your interactive to-do list:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Advanced To-Do List</title>
<style>
body { font-family: Arial, sans-serif; margin: 30px; }
#todoList { list-style: none; padding: 0; }
#todoList li { padding: 10px; margin: 5px; background: #f0f0f0; border: 1px solid #ccc; cursor: grab; }
.dragging { opacity: 0.5; }
</style>
</head>
<body>
<h1>Advanced To-Do List</h1>
<input type="text" id="newTask" placeholder="New Task">
<button id="addTask">Add Task</button>
<ul id="todoList"></ul>
<script>
const todoList = document.getElementById('todoList');
const newTaskInput = document.getElementById('newTask');
const addTaskButton = document.getElementById('addTask');

// Retrieve saved tasks or initialize an empty array.
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];

// Render the tasks on the page.
function renderTasks() {
todoList.innerHTML = '';
tasks.forEach((task, index) => {
const li = document.createElement('li');
li.textContent = task;
li.draggable = true;
li.dataset.index = index;
li.addEventListener('dragstart', dragStart);
li.addEventListener('dragover', dragOver);
li.addEventListener('drop', drop);
todoList.appendChild(li);
});
localStorage.setItem('tasks', JSON.stringify(tasks));
}

// Add a new task.
function addTask() {
const task = newTaskInput.value.trim();
if (task) {
tasks.push(task);
newTaskInput.value = '';
renderTasks();
}
}
addTaskButton.addEventListener('click', addTask);

// Drag & drop functions.
let draggedIndex;
function dragStart(e) {
draggedIndex = e.target.dataset.index;
e.target.classList.add('dragging');
}
function dragOver(e) {
e.preventDefault(); // Necessary to allow dropping.
}
function drop(e) {
e.preventDefault();
const targetIndex = e.target.dataset.index;
if (draggedIndex !== targetIndex) {
// Swap tasks.
[tasks[draggedIndex], tasks[targetIndex]] = [tasks[targetIndex], tasks[draggedIndex]];
renderTasks();
}
}

renderTasks();
</script>
</body>
</html>

📌 Breaking Down the Code

1️⃣ HTML Structure

The HTML is simple:

  • An <input> field for adding new tasks.
  • A <button> to trigger task addition.
  • A <ul> (unordered list) where the tasks are dynamically displayed.
<input type="text" id="newTask" placeholder="New Task">
<button id="addTask">Add Task</button>
<ul id="todoList"></ul>

2️⃣ Styling with CSS

The CSS styles make the to-do list visually appealing:

  • The tasks (<li> elements) have padding, a background, and a border.
  • The .dragging class makes the item semi-transparent when being dragged.
#todoList li {
padding: 10px;
margin: 5px;
background: #f0f0f0;
border: 1px solid #ccc;
cursor: grab;
}
.dragging {
opacity: 0.5;
}

3️⃣ Storing Tasks in Local Storage

To save tasks even after a page refresh, we use the localStorage API. Tasks are stored as an array of strings:

// Retrieve saved tasks or initialize an empty array.
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];

Every time the tasks are updated, they are saved back into localStorage:

localStorage.setItem('tasks', JSON.stringify(tasks));

4️⃣ Adding New Tasks

When the user enters a task and clicks “Add Task”:

  1. The task is trimmed to remove extra spaces.
  2. It’s pushed into the tasks array.
  3. The UI is re-rendered to show the new task.
  4. The task list is saved in local storage.
function addTask() {
const task = newTaskInput.value.trim();
if (task) {
tasks.push(task);
newTaskInput.value = '';
renderTasks();
}
}
addTaskButton.addEventListener('click', addTask);

5️⃣ Implementing Drag & Drop

We use the HTML5 Drag & Drop API to enable reordering tasks.

Dragging a Task

  • When dragging starts, we store the dragged task’s index.
  • The .dragging class is added for visual feedback.
function dragStart(e) {
draggedIndex = e.target.dataset.index;
e.target.classList.add('dragging');
}

Dragging Over Another Task

To allow dropping, we prevent the default behavior.

function dragOver(e) {
e.preventDefault();
}

Dropping a Task in a New Position

  1. We get the target task’s index.
  2. If different from the dragged task:
    • Swap the tasks in the array.
    • Re-render the list.
    • Update local storage.
function drop(e) {
e.preventDefault();
const targetIndex = e.target.dataset.index;
if (draggedIndex !== targetIndex) {
[tasks[draggedIndex], tasks[targetIndex]] = [tasks[targetIndex], tasks[draggedIndex]];
renderTasks();
}
}

🎯 Final Thoughts

This advanced to-do list project showcases: ✅ Local Storage for persistence
Drag & Drop for interactive reordering
Vanilla JavaScript with no external dependencies

🔥 Suggested Enhancements

Want to improve it further? Try adding:

  • Task Deletion (Add a delete button next to each task).
  • Task Completion Toggle (Strike-through completed tasks).
  • Dark Mode for better UI experience.