Build an Interactive Todo List in Vanilla JavaScript

We’re building an Interactive Todo List using Vanilla JavaScript — no frameworks, no fluff, just clean HTML, CSS, and pure JavaScript magic.

This project is fun, fast, and very real-world. Perfect for your portfolio!

Build an Interactive Todo List in Vanilla JavaScript

Features:

  • Add new tasks
  • Mark tasks as completed
  • Delete tasks
  • Filter: All / Active / Completed
  • Persistent with localStorage

We’ll Build It in 5 Steps:

  1. HTML Structure – Input, buttons, list
  2. CSS Styling – Clean and responsive
  3. JS: Add/Delete/Check Tasks
  4. JS: Filter and Manage State
  5. LocalStorage for Saving Tasks

Shall we begin with Step 1: HTML structure? Let’s lay down the bones of our Interactive Todo List.

Step 1: HTML Structure

Here’s the clean and semantic HTML layout for our todo app:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Interactive Todo List</title>
    <link rel="stylesheet" href="style.css" />
</head>
<body>
    
<div class="container">
    <h1>My Todo List</h1>

    <div class="todo-input">
    <input type="text" id="task-input" placeholder="What do you need to do?" />
    <button id="add-task">Add</button>
    </div>

    <ul id="todo-list"></ul>

    <div class="todo-footer">
    <button id="show-all">All</button>
    <button id="show-active">Active</button>
    <button id="show-completed">Completed</button>
    <button id="clear-completed">Clear Completed</button>
    </div>
</div>

<script src="script.js"></script>
</body>
</html>
Code language: HTML, XML (xml)

Quick Overview:

  • #task-input: Where users type a task
  • #add-task: Button to add the task
  • #todo-list: Where tasks appear
  • Footer buttons for filtering and clearing

Up next? ✨
CSS Styling to make it look modern and clean.

Let’s style it up! 🎨💫
Here’s a clean, modern CSS to give your Interactive Todo List a sleek and functional look.

Step 2: CSS Styling (style.css)

/* Google Font for a modern look */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }

body {
    background: #f0f2f5;
    font-family: 'Inter', sans-serif;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    min-height: 100vh;
    padding: 40px;
}

.container {
    background: #fff;
    padding: 30px 25px;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.1);
    width: 100%;
    max-width: 500px;
}

h1 {
    margin-bottom: 20px;
    color: #333;
    font-size: 28px;
    text-align: center;
}

.todo-input {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
}

#task-input {
    flex: 1;
    padding: 12px;
    border: 1px solid #ccc;
    border-radius: 6px;
    font-size: 16px;
}

#add-task {
    padding: 12px 18px;
    background-color: #4caf50;
    color: white;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.3s ease;
}

#add-task:hover {
    background-color: #43a047;  
}

#todo-list {
    list-style: none;
}

#todo-list li {
    background: #f9f9f9;
    padding: 12px;
    margin-bottom: 10px;
    border-radius: 6px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    transition: all 0.2s ease-in-out;
}

#todo-list li.completed {
    text-decoration: line-through;
    color: #888;
    background: #e2e2e2;
}

.todo-footer {
    margin-top: 20px;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: space-between;
}

.todo-footer button {
    flex: 1;
    padding: 10px;
    border: none;
    background-color: #e0e0e0;
    color: #333;
    border-radius: 6px;
    cursor: pointer;
    font-size: 14px;
    transition: background 0.3s ease;
}

.todo-footer button:hover {
    background-color: #d5d5d5;
}
Code language: CSS (css)

Your todo list should now look clean, centered, and responsive — ready for some interactivity. 🎯

Ready for the fun part?

Yessss! Let’s bring this Todo List to life with Vanilla JavaScript 🧠⚡

This script will handle:

  • Adding tasks
  • Marking them as completed
  • Deleting tasks
  • Filtering: All / Active / Completed
  • Saving to localStorage

Step 3: JavaScript Logic (script.js)

// Select DOM elements
const input = document.getElementById("task-input");
const addBtn = document.getElementById("add-task");
const list = document.getElementById("todo-list");
const showAll = document.getElementById("show-all");
const showActive = document.getElementById("show-active");
const showCompleted = document.getElementById("show-completed");
const clearCompleted = document.getElementById("clear-completed");

// Load from localStorage
let todos = JSON.parse(localStorage.getItem("todos")) || [];

// Render tasks
function renderTodos(filter = "all") {
    list.innerHTML = "";
    todos.forEach((todo, index) => {
        if (
        (filter === "active" && todo.completed) ||
        (filter === "completed" && !todo.completed)
        ) return;

        const li = document.createElement("li");
        li.className = todo.completed ? "completed" : "";
        li.innerHTML = `
        <span>${todo.text}</span>
        <div>
            <button onclick="toggleComplete(${index})">✔️</button>
            <button onclick="deleteTodo(${index})">🗑️</button>
        </div>
        `;
        list.appendChild(li);
    });
}

// Add new task
addBtn.addEventListener("click", () => {
    const text = input.value.trim();
    if (text !== "") {
        todos.push({ text, completed: false });
        input.value = "";
        saveAndRender();
    }
});

// Mark task as complete/incomplete
function toggleComplete(index) {
    todos[index].completed = !todos[index].completed;
    saveAndRender();
}

// Delete task
function deleteTodo(index) {
    todos.splice(index, 1);
    saveAndRender();
}

// Save to localStorage and re-render
function saveAndRender() {
    localStorage.setItem("todos", JSON.stringify(todos));
    renderTodos(currentFilter);
}

// Filters
let currentFilter = "all";
showAll.addEventListener("click", () => {
    currentFilter = "all";
    renderTodos("all");
});
showActive.addEventListener("click", () => {
    currentFilter = "active";
    renderTodos("active");
});
showCompleted.addEventListener("click", () => {
    currentFilter = "completed";
    renderTodos("completed");
});
clearCompleted.addEventListener("click", () => {
    todos = todos.filter(todo => !todo.completed);
    saveAndRender();
});

// Initial load
renderTodos();
Code language: JavaScript (javascript)

🎉 Done!

You now have a fully interactive, filterable, and persistent Todo List App — built with pure HTML, CSS, and JavaScript.


Discover more from Prime Inspire

Subscribe to get the latest posts sent to your email.

We’d love to hear your thoughts! Share your ideas below 💡

Scroll to Top