🧠 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.
Top comments (6)
Ah yes, the Todo app. Here's one I concocted (vanilla JS, uses event delegation for handling stuff)
Looks good! Clean and simple I like how you handled the logic—simple but effective.
pretty cool honestly, sometimes i forget how much you can get done with just pure js - you ever feel like using frameworks kinda makes you skip learning the basics?
Yes I skip a lot earlier, but right now i came back to fundamentals , Frameworks also makes developer life easy in so many like we don't need to write all the boiler plate code and so many other things but foundation is necessary too , so i'm trying to keep the balance now.
Love how you kept it truly minimal and readable, especially the localStorage part.
Have you ever tried adding basic drag-and-drop for reordering tasks as a next step?
Thanks! Glad you liked the localStorage part—it took some tweaking to keep it clean.
Drag-and-drop is definitely on my list, might try a lightweight approach next!