CodingBanana
CodingBanana
JavaScript

Project: To-Do App

20 min read📖 Beginner
In this project you'll build a To-Do App step by step — from a blank page to a fully working app where you can add tasks, mark them complete, delete them, and even save them so they persist when you close the browser.

This project uses everything you've learned: variables, functions, arrays, objects, DOM manipulation, and events. By the end, you'll have a real app you can show to others.

🎯

Follow each step in order. Each step builds on the previous one. After each step, try the Try It Yourself section to experiment with what you've built.

Step 1 — HTML Structure

Every app needs a structure. Create the HTML skeleton first — an input field, a button, and a list to hold the tasks:

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

    <div class="input-row">
      <input type="text" id="taskInput" placeholder="Add a new task...">
      <button id="addBtn">Add</button>
    </div>

    <ul id="taskList"></ul>
  </div>

  <script src="app.js"></script>
</body>
</html>

This gives us:

  • A div.app wrapper to center everything
  • An input where users type new tasks
  • An Add button
  • An empty ul where tasks will appear

Step 2 — Styling

Add CSS to make the app look clean and polished. Create a style.css file:

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

body {
  font-family: "Nunito", sans-serif;
  background: #f8fafc;
  display: flex;
  justify-content: center;
  padding: 40px 16px;
  min-height: 100vh;
}

.app {
  background: white;
  border-radius: 16px;
  padding: 32px;
  width: 100%;
  max-width: 480px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
  height: fit-content;
}

h1 {
  font-size: 1.75rem;
  color: #0f172a;
  margin-bottom: 24px;
}

.input-row {
  display: flex;
  gap: 8px;
  margin-bottom: 24px;
}

input[type="text"] {
  flex: 1;
  padding: 10px 14px;
  border: 2px solid #e5e7eb;
  border-radius: 10px;
  font-size: 1rem;
  font-family: inherit;
  outline: none;
  transition: border-color 0.2s;
}

input[type="text"]:focus {
  border-color: #6367ff;
}

button {
  background: #6367ff;
  color: white;
  border: none;
  border-radius: 10px;
  padding: 10px 18px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
  font-family: inherit;
}

button:hover {
  background: #8494ff;
}

ul {
  list-style: none;
}

.task-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid #f1f5f9;
}

.task-item:last-child {
  border-bottom: none;
}

.task-check {
  width: 22px;
  height: 22px;
  border: 2px solid #6367ff;
  border-radius: 6px;
  cursor: pointer;
  background: white;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s;
}

.task-check.checked {
  background: #6367ff;
}

.task-check.checked::after {
  content: "✓";
  color: white;
  font-size: 14px;
  font-weight: 700;
}

.task-text {
  flex: 1;
  font-size: 1rem;
  color: #0f172a;
  transition: color 0.2s;
}

.task-text.done {
  text-decoration: line-through;
  color: #94a3b8;
}

.delete-btn {
  background: none;
  border: none;
  color: #cbd5e1;
  font-size: 1.2rem;
  cursor: pointer;
  padding: 0 4px;
  transition: color 0.2s;
  line-height: 1;
}

.delete-btn:hover {
  background: none;
  color: #ef4444;
}

Step 3 — Adding Tasks

Create app.js and start by wiring up the Add button. When clicked, it should create a new task and add it to the list:

// Select the elements we need
const taskInput = document.getElementById("taskInput");
const addBtn = document.getElementById("addBtn");
const taskList = document.getElementById("taskList");

// Our tasks are stored as an array of objects
let tasks = [];

// Add a task
function addTask() {
  const text = taskInput.value.trim();

  if (!text) return;   // do nothing if input is empty

  const task = {
    id: Date.now(),    // unique ID based on timestamp
    text: text,
    done: false,
  };

  tasks.push(task);
  taskInput.value = "";   // clear the input
  renderTasks();
}

// Render all tasks to the page
function renderTasks() {
  taskList.innerHTML = "";   // clear the list

  tasks.forEach((task) => {
    const li = document.createElement("li");
    li.className = "task-item";
    li.innerHTML = `
      <div class="task-check ${task.done ? 'checked' : ''}" data-id="${task.id}"></div>
      <span class="task-text ${task.done ? 'done' : ''}">${task.text}</span>
      <button class="delete-btn" data-id="${task.id}">×</button>
    `;
    taskList.appendChild(li);
  });
}

// Listen for button click
addBtn.addEventListener("click", addTask);

// Also allow pressing Enter to add
taskInput.addEventListener("keydown", (event) => {
  if (event.key === "Enter") addTask();
});

Now when you click Add or press Enter, a new task appears in the list.

Step 4 — Completing Tasks

Add click handling so users can tick off tasks. Use event delegation on the list — one listener handles all checkboxes:

// Toggle a task's done state
function toggleTask(id) {
  tasks = tasks.map((task) =>
    task.id === id ? { ...task, done: !task.done } : task
  );
  renderTasks();
}

// Listen for clicks on the task list
taskList.addEventListener("click", (event) => {
  const check = event.target.closest(".task-check");
  const deleteBtn = event.target.closest(".delete-btn");

  if (check) {
    const id = Number(check.dataset.id);
    toggleTask(id);
  }

  if (deleteBtn) {
    const id = Number(deleteBtn.dataset.id);
    deleteTask(id);
  }
});

Clicking the checkbox now toggles the task between done and not done, with a strikethrough style.

Step 5 — Deleting Tasks

Add the delete function. The × button removes a task from the array:

// Remove a task by id
function deleteTask(id) {
  tasks = tasks.filter((task) => task.id !== id);
  renderTasks();
}

Now clicking × removes the task immediately. Notice we're using filter to create a new array without that task — a clean, immutable pattern.

Try It Yourself — Steps 1–5

Step 6 — Saving to localStorage

Right now, tasks disappear when you refresh the page. Fix that with localStorage — a simple browser storage that persists across sessions:

// Save tasks to localStorage
function saveTasks() {
  localStorage.setItem("tasks", JSON.stringify(tasks));
}

// Load tasks from localStorage
function loadTasks() {
  const saved = localStorage.getItem("tasks");
  if (saved) {
    tasks = JSON.parse(saved);
    renderTasks();
  }
}

// Call saveTasks after every change
function addTask() {
  const text = taskInput.value.trim();
  if (!text) return;
  tasks.push({ id: Date.now(), text, done: false });
  taskInput.value = "";
  renderTasks();
  saveTasks();   // ← add this
}

function toggleTask(id) {
  tasks = tasks.map((task) =>
    task.id === id ? { ...task, done: !task.done } : task
  );
  renderTasks();
  saveTasks();   // ← add this
}

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

// Load saved tasks when the page opens
loadTasks();

localStorage.setItem stores a string — so we convert our array to JSON with JSON.stringify. When loading, JSON.parse converts it back to an array.

Step 7 — Task Counter

Add a small counter showing how many tasks are remaining:

// Add this to your HTML above the ul:
// <p id="taskCount"></p>

function updateCount() {
  const remaining = tasks.filter((t) => !t.done).length;
  const total = tasks.length;
  const countEl = document.getElementById("taskCount");

  if (total === 0) {
    countEl.textContent = "No tasks yet. Add one above!";
  } else {
    countEl.textContent = `${remaining} of ${total} remaining`;
  }
}

// Call updateCount() inside renderTasks()
function renderTasks() {
  taskList.innerHTML = "";
  tasks.forEach((task) => { /* ... same as before ... */ });
  updateCount();   // ← add this
}

Complete app.js

Here's the full final version of your JavaScript file:

const taskInput = document.getElementById("taskInput");
const addBtn = document.getElementById("addBtn");
const taskList = document.getElementById("taskList");
const taskCount = document.getElementById("taskCount");

let tasks = [];

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

function loadTasks() {
  const saved = localStorage.getItem("tasks");
  if (saved) tasks = JSON.parse(saved);
}

function updateCount() {
  const remaining = tasks.filter((t) => !t.done).length;
  const total = tasks.length;
  if (total === 0) {
    taskCount.textContent = "No tasks yet. Add one above!";
  } else {
    taskCount.textContent = `${remaining} of ${total} remaining`;
  }
}

function renderTasks() {
  taskList.innerHTML = "";

  tasks.forEach((task) => {
    const li = document.createElement("li");
    li.className = "task-item";
    li.innerHTML = `
      <div class="task-check ${task.done ? "checked" : ""}" data-id="${task.id}"></div>
      <span class="task-text ${task.done ? "done" : ""}">${task.text}</span>
      <button class="delete-btn" data-id="${task.id}">×</button>
    `;
    taskList.appendChild(li);
  });

  updateCount();
}

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

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

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

taskList.addEventListener("click", (event) => {
  const check = event.target.closest(".task-check");
  const del = event.target.closest(".delete-btn");
  if (check) toggleTask(Number(check.dataset.id));
  if (del) deleteTask(Number(del.dataset.id));
});

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

loadTasks();
renderTasks();

Challenges

🎉

Congratulations! You've built a fully working To-Do app using HTML, CSS, and JavaScript. You've used variables, arrays, objects, DOM manipulation, events, and localStorage. These are the core skills of frontend web development.

Have anything to say about this lesson?

Your feedback helps improve these tutorials. If something was confusing or missing, let us know.

We don't currently reply to feedback — but if we add that feature in the future, we'll reach out to you.