Beginner Project

Build a To-Do List App

Learn to create a fully functional task management app with JavaScript, localStorage, and filtering!

Try the To-Do List App

    No tasks yet. Add your first task above!

What You'll Learn

Add Tasks

Create new tasks dynamically

Edit Tasks

Update task text inline

Delete Tasks

Remove completed tasks

Mark Complete

Toggle task completion

Filter Tasks

View all, active, or completed

Persistence

Save tasks with localStorage

Step-by-Step Tutorial

Step 1: HTML Structure

Create the input field, add button, filters, and task list container:

<div class="todo-app">
  <div class="todo-header">
    <input type="text" id="taskInput" placeholder="Enter task..." />
    <button id="addBtn">Add Task</button>
  </div>
  
  <div class="filters">
    <button class="filter-btn active" data-filter="all">All</button>
    <button class="filter-btn" data-filter="active">Active</button>
    <button class="filter-btn" data-filter="completed">Completed</button>
  </div>
  
  <ul class="task-list" id="taskList"></ul>
</div>

Step 2: Initialize JavaScript

Set up the tasks array and load from localStorage:

let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentFilter = 'all';

const taskInput = document.getElementById('taskInput');
const addBtn = document.getElementById('addBtn');
const taskList = document.getElementById('taskList');

// Load tasks on page load
renderTasks();

Step 3: Add Task Function

Create function to add new tasks with unique IDs:

function addTask() {
    const taskText = taskInput.value.trim();
    if (taskText === '') return;
    
    const task = {
        id: Date.now(),
        text: taskText,
        completed: false
    };
    
    tasks.push(task);
    saveTasks();
    renderTasks();
    taskInput.value = '';
}

addBtn.addEventListener('click', addTask);
taskInput.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') addTask();
});

Step 4: Render Tasks Function

Display tasks with filtering and event handlers:

function renderTasks() {
    const filteredTasks = tasks.filter(task => {
        if (currentFilter === 'active') return !task.completed;
        if (currentFilter === 'completed') return task.completed;
        return true;
    });
    
    if (filteredTasks.length === 0) {
        taskList.innerHTML = '<div class="empty-state">No tasks!</div>';
        return;
    }
    
    taskList.innerHTML = filteredTasks.map(task => `
        <li class="task-item ${task.completed ? 'completed' : ''}">
            <input type="checkbox" 
                   class="task-checkbox" 
                   ${task.completed ? 'checked' : ''} 
                   onchange="toggleTask(${task.id})">
            <span class="task-text">${task.text}</span>
            <div class="task-actions">
                <button class="edit-btn" onclick="editTask(${task.id})">Edit</button>
                <button class="delete-btn" onclick="deleteTask(${task.id})">Delete</button>
            </div>
        </li>
    `).join('');
}

Step 5: Task Actions

Implement toggle, edit, delete, and filter functions:

function toggleTask(id) {
    tasks = tasks.map(task => 
        task.id === id ? { ...task, completed: !task.completed } : task
    );
    saveTasks();
    renderTasks();
}

function editTask(id) {
    const task = tasks.find(t => t.id === id);
    const newText = prompt('Edit task:', task.text);
    if (newText !== null && newText.trim() !== '') {
        tasks = tasks.map(t => 
            t.id === id ? { ...t, text: newText.trim() } : t
        );
        saveTasks();
        renderTasks();
    }
}

function deleteTask(id) {
    tasks = tasks.filter(task => task.id !== id);
    saveTasks();
    renderTasks();
}

function saveTasks() {
    localStorage.setItem('tasks', JSON.stringify(tasks));
}

// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
    btn.addEventListener('click', () => {
        document.querySelectorAll('.filter-btn').forEach(b => 
            b.classList.remove('active')
        );
        btn.classList.add('active');
        currentFilter = btn.dataset.filter;
        renderTasks();
    });
});

Complete Source Code

Copy and paste this complete code into a new HTML file:

<!DOCTYPE html>
<html>
<head>
    <title>To-Do List App</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, sans-serif; background: #f1f5f9; padding: 2rem; }
        .todo-app { max-width: 600px; margin: 0 auto; background: white; padding: 2rem; border-radius: 15px; }
        .todo-header { display: flex; gap: 1rem; margin-bottom: 2rem; }
        #taskInput { flex: 1; padding: 1rem; border: 2px solid #e2e8f0; border-radius: 10px; font-size: 1rem; }
        #addBtn { padding: 1rem 2rem; background: linear-gradient(135deg, #a8edea, #fed6e3); border: none; border-radius: 10px; font-weight: 600; cursor: pointer; }
        .filters { display: flex; gap: 1rem; margin-bottom: 1.5rem; justify-content: center; }
        .filter-btn { padding: 0.5rem 1.5rem; background: white; border: 2px solid #e2e8f0; border-radius: 50px; cursor: pointer; }
        .filter-btn.active { background: linear-gradient(135deg, #a8edea, #fed6e3); }
        .task-list { list-style: none; }
        .task-item { background: #f1f5f9; padding: 1rem; border-radius: 10px; margin-bottom: 0.75rem; display: flex; align-items: center; gap: 1rem; }
        .task-item.completed .task-text { text-decoration: line-through; opacity: 0.6; }
        .task-text { flex: 1; }
        .task-actions { display: flex; gap: 0.5rem; }
        .edit-btn, .delete-btn { padding: 0.5rem 1rem; border: none; border-radius: 5px; cursor: pointer; color: white; }
        .edit-btn { background: #3b82f6; }
        .delete-btn { background: #ef4444; }
    </style>
</head>
<body>
    <div class="todo-app">
        <h1 style="text-align:center; margin-bottom:2rem;">My To-Do List</h1>
        <div class="todo-header">
            <input type="text" id="taskInput" placeholder="Enter a new task..." />
            <button id="addBtn">Add Task</button>
        </div>
        <div class="filters">
            <button class="filter-btn active" data-filter="all">All</button>
            <button class="filter-btn" data-filter="active">Active</button>
            <button class="filter-btn" data-filter="completed">Completed</button>
        </div>
        <ul class="task-list" id="taskList"></ul>
    </div>

    <script>
        let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
        let currentFilter = 'all';
        const taskInput = document.getElementById('taskInput');
        const addBtn = document.getElementById('addBtn');
        const taskList = document.getElementById('taskList');

        function addTask() {
            const taskText = taskInput.value.trim();
            if (taskText === '') return;
            tasks.push({ id: Date.now(), text: taskText, completed: false });
            saveTasks();
            renderTasks();
            taskInput.value = '';
        }

        function renderTasks() {
            const filtered = tasks.filter(t => {
                if (currentFilter === 'active') return !t.completed;
                if (currentFilter === 'completed') return t.completed;
                return true;
            });
            taskList.innerHTML = filtered.length === 0 ? '<p style="text-align:center;padding:2rem;color:#64748b;">No tasks</p>' :
                filtered.map(t => `
                    <li class="task-item ${t.completed ? 'completed' : ''}">
                        <input type="checkbox" ${t.completed ? 'checked' : ''} onchange="toggleTask(${t.id})">
                        <span class="task-text">${t.text}</span>
                        <div class="task-actions">
                            <button class="edit-btn" onclick="editTask(${t.id})">Edit</button>
                            <button class="delete-btn" onclick="deleteTask(${t.id})">Delete</button>
                        </div>
                    </li>
                `).join('');
        }

        function toggleTask(id) {
            tasks = tasks.map(t => t.id === id ? { ...t, completed: !t.completed } : t);
            saveTasks();
            renderTasks();
        }

        function editTask(id) {
            const task = tasks.find(t => t.id === id);
            const newText = prompt('Edit task:', task.text);
            if (newText && newText.trim()) {
                tasks = tasks.map(t => t.id === id ? { ...t, text: newText.trim() } : t);
                saveTasks();
                renderTasks();
            }
        }

        function deleteTask(id) {
            tasks = tasks.filter(t => t.id !== id);
            saveTasks();
            renderTasks();
        }

        function saveTasks() {
            localStorage.setItem('tasks', JSON.stringify(tasks));
        }

        addBtn.addEventListener('click', addTask);
        taskInput.addEventListener('keypress', e => { if (e.key === 'Enter') addTask(); });
        document.querySelectorAll('.filter-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
                currentFilter = btn.dataset.filter;
                renderTasks();
            });
        });

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

Enhancement Ideas