To-Do List in Vanilla JavaScript
Vijay Kumar

Vijay Kumar @vjygour

About: Freelancer @upwork

Location:
India
Joined:
Sep 6, 2024

To-Do List in Vanilla JavaScript

Publish Date: Jun 16
10 6

🧠 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>
Enter fullscreen mode Exit fullscreen mode

🧠 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();
Enter fullscreen mode Exit fullscreen mode

🧠 Problem Solved — Let’s Break It Down

  1. Persistent Data:
    We use localStorage — your tasks stay alive even after closing the tab.

  2. No Frameworks:
    Just JavaScript. Because when you learn this, frameworks become tools, not crutches.

  3. Event-Driven:
    Clicks trigger behavior. Code responds. Simple & reactive.

  4. 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();
});
Enter fullscreen mode Exit fullscreen mode

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.

Comments 6 total

  • KooiInc
    KooiIncJun 16, 2025

    Ah yes, the Todo app. Here's one I concocted (vanilla JS, uses event delegation for handling stuff)

    • Vijay Kumar
      Vijay KumarJun 17, 2025

      Looks good! Clean and simple I like how you handled the logic—simple but effective.

  • Dotallio
    DotallioJun 16, 2025

    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?

    • Vijay Kumar
      Vijay KumarJun 17, 2025

      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!

  • Nevo David
    Nevo DavidJun 17, 2025

    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?

    • Vijay Kumar
      Vijay KumarJun 18, 2025

      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.

Add comment