Task Manager

Build a JavaScript task manager from scratch — DOM manipulation, events, localStorage persistence and class-based structure.

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Intermediate-Advanced Project — Master DOM, Events, localStorage & OOP

🧠 Deep Project Overview

A Task Manager looks simple, but it secretly trains you on 95% of what front-end devs do every day:

Apps like Todoist, Microsoft To Do, ClickUp, Trello all start from this same idea: a list of items with state (done/not done, priority, tags, etc.). If you can build this project cleanly, you're already thinking like a real JS dev.

🧱 Step 1 – Understand the Structure

Your HTML is split into three main zones:

1. Input Area (.input-group)

2. Filter Bar (.filters)

3. Task List (#taskList)

💡 Tip: When you build other projects (Notes app, Budget tracker, Habit tracker), reuse this same 3-zone layout: inputs → controls/filters → results list.

🧠 Step 2 – The Brain: TaskManager Class

All the logic lives inside one class:

class TaskManager {
  constructor() {
    this.tasks = this.loadTasks();
    this.currentFilter = 'all';
    this.render();
  }
}

Each task looks like:

{
  id: 1732012345678,
  text: "Finish JavaScript lesson",
  priority: "high",
  completed: false,
  createdAt: "2025-11-19T11:30:00.000Z"
}

That's already proper data modeling – which is what you'll do with databases later.

💾 Step 3 – Saving and Loading with localStorage

loadTasks() {
  const saved = localStorage.getItem('tasks');
  return saved ? JSON.parse(saved) : [];
}

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

localStorage only stores strings, so you convert:

If the app acts weird, open DevTools → Application tab → Local Storage → clear the 'tasks' key, then refresh.

This pattern (load → modify → save) is identical for: Notes apps, Shopping carts, Watch-later lists, "Recent code snippets" feature

➕ Step 4 – Adding Tasks

addTask(text, priority) {
  if (!text.trim()) {
    alert('Please enter a task!');
    return;
  }

  const task = {
    id: Date.now(),
    text: text.trim(),
    priority,
    completed: false,
    createdAt: new Date().toISOString()
  };

  this.tasks.unshift(task);
  this.saveTasks();
  this.render();
}

What this teaches you:

✅ Step 5 – Completing and Deleting Tasks

toggleTask(id) {
  const task = this.tasks.find(t => t.id === id);
  if (task) {
    task.completed = !task.completed;
    this.saveTasks();
    this.render();
  }
}
deleteTask(id) {
  this.tasks = this.tasks.filter(t => t.id !== id);
  this.saveTasks();
  this.render();
}

🔍 Step 6 – Filters and UI State

getFilteredTasks() {
  switch (this.currentFilter) {
    case 'active':
      return this.tasks.filter(t => !t.completed);
    case 'completed':
      return this.tasks.filter(t => t.completed);
    case 'high':
      return this.tasks.filter(t => t.priority === 'high');
    default:
      return this.tasks;
  }
}
setFilter(filter) {
  this.currentFilter = filter;

  document.querySelectorAll('.filter-btn').forEach(btn => {
    btn.classList.remove('active');
  });
  event.target.classList.add('active');

  this.render();
}

What you're doing here:

Instead of using onclick in HTML, you could attach event listeners in JS and use e.target directly. This is called event delegation – super useful for dynamic content.

🖼️ Step 7 – Rendering HTML from Data

⌨️ Step 8 – Global Functions & Keyboard UX

function addTask() {
  const input = document.getElementById('taskInput');
  const priority = document.getElementById('prioritySelect').value;
  taskManager.addTask(input.value, priority);
  input.value = '';
}

function filterTasks(filter) {
  taskManager.setFilter(filter);
}
document.getElementById('taskInput').addEventListener('keypress', (e) => {
  if (e.key === 'Enter') addTask();
});

This is already nice UX – users can quickly hit Enter instead of clicking.

💻 Try It Yourself

Test the complete Task Manager app below. Study the code, modify it, and experiment with the features!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Task Manager</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { 
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      padding: 20px;
    }
    .container { 
      max-width: 600px; 
      margin: 0 auto; 
      background: white;
      padding: 30px;
      border-radius: 15px;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
    }
    h1 { color: #333; margin-bottom: 25px; text-align: center; }
    .input-group { 
      display: flex; 
      gap: 10px; 
      margin-bottom: 20px; 
    }
    input, select { 
      flex: 1;
      padding: 12px; 
      border: 2px solid #ddd;
      border-radius: 8px;
      font-size: 16px;
    }
    input:focus, select:focus {
      outline: none;
      border-color: #667eea;
    }
    button { 
      padding: 12px 20px; 
      background: #667eea;
      color: white;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      font-size: 16px;
      transition: all 0.3s;
    }
    button:hover { background: #5568d3; transform: translateY(-2px); }
    .filters { 
      display: flex; 
      gap: 10px; 
      margin-bottom: 20px;
      flex-wrap: wrap;
    }
    .filter-btn {
      padding: 8px 16px;
      background: #f0f0f0;
      color: #666;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      transition: all 0.3s;
    }
    .filter-btn.active { background: #667eea; color: white; }
    .task-list { list-style: none; }
    .task-item { 
      background: #f8f9fa;
      padding: 15px;
      margin-bottom: 10px;
      border-radius: 8px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      transition: all 0.3s;
      border-left: 4px solid #667eea;
    }
    .task-item:hover { transform: translateX(5px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
    .task-item.completed { 
      opacity: 0.6;
      text-decoration: line-through;
      border-left-color: #28a745;
    }
    .task-content { flex: 1; }
    .task-actions { 
      display: flex; 
      gap: 8px; 
    }
    .task-actions button { 
      padding: 6px 12px; 
      font-size: 14px; 
    }
    .delete-btn { background: #dc3545; }
    .delete-btn:hover { background: #c82333; }
    .complete-btn { background: #28a745; }
    .complete-btn:hover { background: #218838; }
    .priority { 
      display: inline-block;
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      font-weight: bold;
      margin-left: 10px;
    }
    .priority-high { background: #ff6b6b; color: white; }
    .priority-medium { background: #ffd93d; color: #333; }
    .priority-low { background: #6bcf7f; color: white; }
  </style>
</head>
<body>
  <div class="container">
    <h1>📋 Task Manager</h1>
    
    <div class="input-group">
      <input type="text" id="taskInput" placeholder="Enter a new task..." />
      <select id="prioritySelect">
        <option value="low">Low</option>
        <option value="medium" selected>Medium</option>
        <option value="high">High</option>
      </select>
      <button onclick="addTask()">Add Task</button>
    </div>

    <div class="filters">
      <button class="filter-btn active" onclick="filterTasks('all')">All</button>
      <button class="filter-btn" onclick="filterTasks('active')">Active</button>
      <button class="filter-btn" onclick="filterTasks('completed')">Completed</button>
      <button class="filter-btn" onclick="filterTasks('high')">High Priority</button>
    </div>

    <ul id="taskList" class="task-list"></ul>
  </div>

  <script>
    // Task Manager Class
    class TaskManager {
      constructor() {
        this.tasks = this.loadTasks();
        this.currentFilter = 'all';
        this.render();
      }

      loadTasks() {
        const saved = localStorage.getItem('tasks');
        return saved ? JSON.parse(saved) : [];
      }

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

      addTask(text, priority) {
        if (!text.trim()) {
          alert('Please enter a task!');
          return;
        }
        
        const task = {
          id: Date.now(),
          text: text.trim(),
          priority: priority,
          completed: false,
          createdAt: new Date().toISOString()
        };
        
        this.tasks.unshift(task);
        this.saveTasks();
        this.render();
      }

      toggleTask(id) {
        const task = this.tasks.find(t => t.id === id);
        if (task) {
          task.completed = !task.completed;
          this.saveTasks();
          this.render();
        }
      }

      deleteTask(id) {
        this.tasks = this.tasks.filter(t => t.id !== id);
        this.saveTasks();
        this.render();
      }

      getFilteredTasks() {
        switch(this.currentFilter) {
          case 'active':
            return this.tasks.filter(t => !t.completed);
          case 'completed':
            return this.tasks.filter(t => t.completed);
          case 'high':
            return this.tasks.filter(t => t.priority === 'high');
          default:
            return this.tasks;
        }
      }

      render() {
        const taskList = document.getElementById('taskList');
        const filtered = this.getFilteredTasks();
        
        if (filtered.length === 0) {
          taskList.innerHTML = '<li style="text-align: center; color: #999; padding: 20px;">No tasks to show</li>';
          return;
        }

        taskList.innerHTML = filtered.map(task => `
          <li class="task-item ${task.completed ? 'completed' : ''}">
            <div class="task-content">
              <span>${task.text}</span>
              <span class="priority priority-${task.priority}">${task.priority.toUpperCase()}</span>
            </div>
            <div class="task-actions">
              <button class="complete-btn" onclick="taskManager.toggleTask(${task.id})">
                ${task.completed ? '↺' : '✓'}
              </button>
              <button class="delete-btn" onclick="taskManager.deleteTask(${task.id})">✕</button>
            </div>
          </li>
        `).join('');
      }

      setFilter(filter) {
        this.currentFilter = filter;
        
        // Update button states
        document.querySelectorAll('.filter-btn').forEach(btn => {
          btn.classList.remove('active');
        });
        event.target.classList.add('active');
        
        this.render();
      }
    }

    // Create global instance
    const taskManager = new TaskManager();

    // Global functions for onclick handlers
    function addTask() {
      const input = document.getElementById('taskInput');
      const priority = document.getElementById('prioritySelect').value;
      taskManager.addTask(input.value, priority);
      input.value = '';
    }

    function filterTasks(filter) {
      taskManager.setFilter(filter);
    }

    // Enter key support
    document.getElementById('taskInput').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') addTask();
    });
  </script>
</body>
</html>

🧪 What to Practice / Extend

Here's how to use this project to become scary-good at JavaScript:

1. Add due dates

2. Inline editing

3. Search bar

4. Animations

5. Dark Mode

🧾 Final Checklist for "Job-Ready" Level

Before you call this project done, check:

Related lessons