🧠 Build a Jaw-Dropping To-Do List in Vanilla JavaScript.
Let’s get something straight: there are a million to-do list tutorials out there. Most are bloated, half-baked, or full of libraries that you barely need.
But real developers?
They solve problems with the bare minimum. They build function-first, readable code. That’s what we’re doing today — building a fully functional To-Do List in Vanilla JS from zero to awesome.
💥 The Problem
We want a tool that:
- Adds tasks
- Deletes tasks
- Marks tasks as done
- Stores tasks between page reloads (localStorage)
But we’re not here to copy-paste junk. We’re going to understand and own every line.
🧱 The Structure (HTML)
Here's the minimal layout — no fluff.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vanilla JS To-Do</title>
<style>
body { font-family: sans-serif; background: #111; color: #eee; display: flex; flex-direction: column; align-items: center; margin-top: 50px; }
input, button { padding: 10px; margin: 5px; border-radius: 5px; border: none; }
ul { list-style: none; padding: 0; max-width: 300px; width: 100%; }
li { background: #222; margin: 5px 0; padding: 10px; display: flex; justify-content: space-between; align-items: center; border-radius: 5px; }
li.done { text-decoration: line-through; opacity: 0.6; }
.remove-btn { background: crimson; color: white; cursor: pointer; }
</style>
</head>
<body>
<h1>🧠 Just Do It</h1>
<input type="text" id="taskInput" placeholder="Enter task" />
<button id="addBtn">Add Task</button>
<ul id="taskList"></ul>
<script src="app.js"></script>
</body>
</html>
🧠 The Logic (JavaScript)
Now we enter the core battle ground: app.js
const taskInput = document.getElementById('taskInput');
const addBtn = document.getElementById('addBtn');
const taskList = document.getElementById('taskList');
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
// 🛠 Render tasks from array
function renderTasks() {
taskList.innerHTML = ''; // Clear previous
tasks.forEach((task, index) => {
const li = document.createElement('li');
li.className = task.done ? 'done' : '';
li.innerHTML = `
<span>${task.text}</span>
<div>
<button onclick="toggleDone(${index})">✔</button>
<button class="remove-btn" onclick="deleteTask(${index})">✖</button>
</div>
`;
taskList.appendChild(li);
});
localStorage.setItem('tasks', JSON.stringify(tasks));
}
// ➕ Add task
addBtn.addEventListener('click', () => {
const text = taskInput.value.trim();
if (text === '') return alert('Enter something useful!');
tasks.push({ text, done: false });
taskInput.value = '';
renderTasks();
});
// ✅ Toggle task status
function toggleDone(index) {
tasks[index].done = !tasks[index].done;
renderTasks();
}
// ❌ Delete task
function deleteTask(index) {
tasks.splice(index, 1);
renderTasks();
}
// 🔁 On load
renderTasks();
🧠 Problem Solved — Let’s Break It Down
Persistent Data:
We uselocalStorage
— your tasks stay alive even after closing the tab.No Frameworks:
Just JavaScript. Because when you learn this, frameworks become tools, not crutches.Event-Driven:
Clicks trigger behavior. Code responds. Simple & reactive.Clean UX:
-
✔
to toggle done -
✖
to delete - Tasks saved automatically
⚡ Bonus Power: Keyboard Add
Want to make it snappy? Add this:
taskInput.addEventListener('keypress', e => {
if (e.key === 'Enter') addBtn.click();
});
Now you can hit Enter to add a task — like a real productivity machine.
🚀 Final Words
You didn’t just make a to-do list.
You wrote a real app with zero dependencies, a clean mental model, and real-world utility.
This is how a problem-solver thinks: less fluff, more flow.
Ah yes, the Todo app. Here's one I concocted (vanilla JS, uses event delegation for handling stuff)