Final Mini Project: To-Do List Application0%
Final Mini Project: To-Do List Application
Beginner12 min read•Updated: Jul 11, 2026
Capstone Project: Production-Grade To-Do List Application
Congratulations on reaching the capstone project of the JavaScript for Beginners course! In this project, we combine all the fundamental concepts we've learned—variables, arrays, objects, functions, event handling, DOM manipulation, and localStorage—into a complete, commercial-grade To-Do List Web Application.
1. Application Features
Our Capstone To-Do Application includes:
- 1Task Management: Add tasks, delete tasks, and toggle completed status.
- 2Category Filtering: Filter tasks by All, Active, and Completed.
- 3Local Storage Persistence: Automatically serialize and persist task state across sessions.
- 4Task Counter & Cleanup: Show real-time active task counts and provide a "Clear Completed" button.
- 5Empty State: Beautiful UI when no tasks exist.
2. The HTML Structure (index.html)
3. CSS Styling (style.css)
CSS
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, sans-serif;
}
body {
background: #0f172a;
color: #f8fafc;
min-height: 100vh;
display: flex;
justify-content: center;
padding: 3rem 1rem;
}
.app-container {
background: #1e293b;
width: 100%;
max-width: 520px;
border-radius: 1rem;
padding: 2rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
.app-header h1 {
font-size: 2rem;
color: #38bdf8;
margin-bottom: 0.25rem;
}
.app-header p {
font-size: 0.875rem;
color: #94a3b8;
margin-bottom: 1.5rem;
}
#todo-form {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
#todo-input {
flex: 1;
padding: 0.75rem 1rem;
background: #0f172a;
border: 1px solid #334155;
border-radius: 0.5rem;
color: #fff;
font-size: 1rem;
}
#todo-input:focus {
outline: none;
border-color: #38bdf8;
}
#add-btn {
background: #0284c7;
color: white;
border: none;
padding: 0.75rem 1.25rem;
border-radius: 0.5rem;
font-weight: 600;
cursor: pointer;
}
.filters {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid #334155;
padding-bottom: 0.75rem;
}
.filter-btn {
background: transparent;
border: none;
color: #94a3b8;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-weight: 500;
border-radius: 0.25rem;
}
.filter-btn.active {
color: #38bdf8;
background: rgba(56, 189, 248, 0.1);
}
.todo-list {
list-style: none;
max-height: 350px;
overflow-y: auto;
}
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
background: #0f172a;
border-radius: 0.5rem;
margin-bottom: 0.5rem;
}
.todo-item.completed span {
text-decoration: line-through;
color: #64748b;
}
.todo-text {
flex: 1;
margin: 0 0.75rem;
cursor: pointer;
}
.delete-btn {
background: transparent;
border: none;
color: #ef4444;
cursor: pointer;
font-size: 1.25rem;
}
.app-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid #334155;
font-size: 0.875rem;
color: #94a3b8;
}
.clear-btn {
background: transparent;
border: none;
color: #94a3b8;
cursor: pointer;
}
.clear-btn:hover { color: #f8fafc; }
4. The Complete JavaScript Application (app.js)
JavaScript
// 1. State Management
let todos = JSON.parse(localStorage.getItem("todos_data")) || [];
let currentFilter = "all";
// 2. Select DOM Elements
const todoForm = document.querySelector("#todo-form");
const todoInput = document.querySelector("#todo-input");
const todoList = document.querySelector("#todo-list");
const itemsLeftSpan = document.querySelector("#items-left");
const filterBtns = document.querySelectorAll(".filter-btn");
const clearCompletedBtn = document.querySelector("#clear-completed-btn");
// 3. Helper: Save State to LocalStorage
function saveTodos() {
localStorage.setItem("todos_data", JSON.stringify(todos));
}
// 4. Render Function: Renders UI based on Filter and State
function render() {
todoList.innerHTML = "";
// Filter tasks based on current tab
const filteredTodos = todos.filter((todo) => {
if (currentFilter === "active") return !todo.completed;
if (currentFilter === "completed") return todo.completed;
return true; // 'all'
});
if (filteredTodos.length === 0) {
todoList.innerHTML = `<li style="text-align: center; color: #64748b; padding: 1rem;">No tasks found</li>`;
}
filteredTodos.forEach((todo) => {
const li = document.createElement("li");
li.classList.add("todo-item");
if (todo.completed) li.classList.add("completed");
li.innerHTML = `
<input type="checkbox" class="toggle-check" ${todo.completed ? "checked" : ""}>
<span class="todo-text">${escapeHTML(todo.text)}</span>
<button class="delete-btn" aria-label="Delete Task">×</button>
`;
// Toggle Task Status
const checkbox = li.querySelector(".toggle-check");
checkbox.addEventListener("change", () => toggleTodo(todo.id));
// Delete Task
const deleteBtn = li.querySelector(".delete-btn");
deleteBtn.addEventListener("click", () => deleteTodo(todo.id));
todoList.appendChild(li);
});
// Update active count badge
const activeCount = todos.filter((t) => !t.completed).length;
itemsLeftSpan.textContent = `${activeCount} item${activeCount === 1 ? "" : "s"} left`;
}
// Security: Prevent XSS
function escapeHTML(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
// 5. Actions: Add, Toggle, Delete, Clear
function addTodo(text) {
const newTodo = {
id: Date.now().toString(),
text: text.trim(),
completed: false
};
todos.push(newTodo);
saveTodos();
render();
}
function toggleTodo(id) {
todos = todos.map((todo) => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
saveTodos();
render();
}
function deleteTodo(id) {
todos = todos.filter((todo) => todo.id !== id);
saveTodos();
render();
}
function clearCompleted() {
todos = todos.filter((todo) => !todo.completed);
saveTodos();
render();
}
// 6. Event Listeners
todoForm.addEventListener("submit", (e) => {
e.preventDefault();
const text = todoInput.value.trim();
if (text.length > 0) {
addTodo(text);
todoInput.value = "";
}
});
filterBtns.forEach((btn) => {
btn.addEventListener("click", () => {
filterBtns.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
currentFilter = btn.dataset.filter;
render();
});
});
clearCompletedBtn.addEventListener("click", clearCompleted);
// Initial Load
render();
Interactive Quiz
Practice Quiz
Question 1
How does
JSON.stringify(todos) assist with localStorage persistence?Question 2
What technique is used to generate unique IDs for each task item in
Date.now().toString()?Question 3
Why does
todoForm.addEventListener("submit", ...) call e.preventDefault()?Question 4
Which array method is used to filter out completed tasks when running
clearCompleted()?Question 5
What does the spread syntax
{ ...todo, completed: !todo.completed } accomplish inside toggleTodo?Related Lessons
| Previous Lesson | Next Lesson |
|---|---|
| Image Slider (Basic) | None |
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.
PreviousPrevNext