Building a To-Do List App with JavaScript: A Step-by-Step Guide

In the world of web development, building a to-do list app is a classic project that serves as an excellent introduction to essential programming concepts. It’s simple enough to understand quickly yet complex enough to teach you about DOM manipulation, event handling, data storage, and JavaScript logic. This project bridges the gap between theory and practical application, giving you a tangible outcome you can build on.

This article offers a thorough, step-by-step guide to building a to-do list app using vanilla JavaScript—no frameworks, no libraries—just plain HTML, CSS, and JavaScript. Along the way, you’ll learn not only how to make a working app but also best practices for structuring your code and improving user experience.

1. Understanding the To-Do List App

Before diving into code, it’s important to clarify what a to-do list app entails. At its core, this app allows users to:

  • Add new tasks
  • View a list of tasks
  • Mark tasks as complete or incomplete
  • Remove tasks from the list
  • Optionally, persist data so tasks remain after refreshing the page

These features seem simple, but each involves essential programming concepts that are vital for any budding developer.

2. Setting Up the Project Structure

Our project will include three basic files:

  • index.html: The structure of the app (HTML)
  • styles.css: The styling of the app (CSS)
  • app.js: The logic of the app (JavaScript)

This separation helps keep your code organized and easier to maintain.

3. Building the HTML Structure

The HTML file sets up the skeleton of our to-do list. Here’s a minimal but effective structure:

htmlCopy<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>To-Do List App</title>
    <link rel="stylesheet" href="styles.css" />
</head>
<body>
    <div class="container">
        <h1>My To-Do List</h1>
        <form id="todo-form">
            <input type="text" id="todo-input" placeholder="Add a new task" required />
            <button type="submit">Add</button>
        </form>
        <ul id="todo-list"></ul>
    </div>

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

Key elements:

  • An input field for users to type their task
  • A button to submit the new task
  • An unordered list (<ul>) to display tasks dynamically

4. Styling with CSS

While the main focus is on JavaScript, a little CSS makes the app user-friendly. Here’s an example to get started:

cssCopybody {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 0;
}

.container {
    max-width: 500px;
    margin: 50px auto;
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    color: #333;
}

#todo-form {
    display: flex;
    justify-content: space-between;
}

#todo-input {
    flex: 1;
    padding: 10px;
    font-size: 16px;
}

button {
    padding: 10px 20px;
    background-color: #5c8df6;
    color: white;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #3a6be0;
}

#todo-list {
    list-style: none;
    padding: 0;
    margin-top: 20px;
}

#todo-list li {
    padding: 10px;
    background-color: #eee;
    border-bottom: 1px solid #ddd;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

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

.delete-btn {
    background: transparent;
    border: none;
    color: #cc0000;
    font-weight: bold;
    cursor: pointer;
}

5. Adding JavaScript Logic

Now we get to the heart of the app: JavaScript. We will cover how to:

  • Add new tasks to the list
  • Mark tasks as complete
  • Delete tasks
  • Persist tasks using localStorage

5.1 Accessing DOM Elements

At the start of app.js, we reference the important DOM elements:

javascriptCopyconst todoForm = document.getElementById('todo-form');
const todoInput = document.getElementById('todo-input');
const todoList = document.getElementById('todo-list');

5.2 Handling Form Submission

When the user submits the form, we want to add the new task.

javascriptCopytodoForm.addEventListener('submit', function(event) {
    event.preventDefault();

    const taskText = todoInput.value.trim();
    if (taskText !== '') {
        addTask(taskText);
        todoInput.value = '';
    }
});

Here we:

  • Prevent the default page reload behavior
  • Trim the input value to avoid empty tasks
  • Call an addTask() function to insert the task
  • Clear the input field for new entries

5.3 Adding a Task to the DOM

The addTask function creates new elements representing the task and appends them to the task list.

javascriptCopyfunction addTask(taskText) {
    const li = document.createElement('li');
    li.textContent = taskText;

    // Add a delete button
    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = 'X';
    deleteBtn.className = 'delete-btn';

    li.appendChild(deleteBtn);
    todoList.appendChild(li);

    // Save to localStorage
    saveTasks();

    // Add event listener for completion toggle
    li.addEventListener('click', function() {
        li.classList.toggle('completed');
        saveTasks();
    });

    // Delete task event
    deleteBtn.addEventListener('click', function(event) {
        event.stopPropagation();  // Prevent triggering completion toggle
        todoList.removeChild(li);
        saveTasks();
    });
}
  • Clicking on the task toggles its completed status.
  • Clicking the delete button removes the task.
  • We call saveTasks() to persist changes.

5.4 Persisting Tasks with localStorage

To ensure tasks remain after page reloads, we store them in the browser’s localStorage.

javascriptCopyfunction saveTasks() {
    const tasks = [];
    todoList.querySelectorAll('li').forEach(li => {
        tasks.push({
            text: li.firstChild.textContent,
            completed: li.classList.contains('completed')
        });
    });
    localStorage.setItem('tasks', JSON.stringify(tasks));
}

function loadTasks() {
    const tasks = JSON.parse(localStorage.getItem('tasks')) || [];
    tasks.forEach(task => {
        addTask(task.text);
        if (task.completed) {
            todoList.lastChild.classList.add('completed');
        }
    });
}

loadTasks();

Here, we:

  • Serialize the task list to JSON and save it.
  • Load saved tasks on page load.
  • Restore completed states.

6. Enhancing User Experience

The basic app works well, but there are ways to make it more user-friendly and powerful.

6.1 Adding Edit Functionality

Users often want to edit tasks after adding them. We can add an edit button and allow inline editing.

Implementation outline:

  • Add an edit button next to each task.
  • Clicking it changes the task into an input field.
  • Pressing Enter or clicking outside saves the change.

6.2 Sorting and Filtering Tasks

Adding filters (e.g., show all, completed, or pending tasks) improves usability.

  • Use buttons or dropdowns for filter options.
  • Update the displayed list based on the selected filter.

6.3 Keyboard Accessibility

Allow users to add tasks by pressing the Enter key.

  • Already handled in form submission with the input field.
  • Consider adding keyboard shortcuts for toggling or deleting tasks.

6.4 Mobile Responsiveness

Use media queries to ensure the app works well on different screen sizes.

7. Testing and Debugging

Testing your to-do list app ensures it works as expected.

  • Use browser developer tools to debug JavaScript.
  • Check console for errors.
  • Test adding, toggling, deleting, and persisting tasks.
  • Test on multiple browsers and devices.

8. Conclusion

Building a to-do list app is an excellent way to learn core JavaScript skills like DOM manipulation, event handling, and data persistence. This step-by-step guide has taken you through creating a functional, interactive app from scratch, laying the foundation for more complex projects.

With this solid base, you can explore advanced topics like using frameworks (React, Vue), integrating with backend services, or enhancing UX through animations and accessibility.

9. Summary Checklist

  • Setup HTML, CSS, and JS files.
  • Select DOM elements using getElementById.
  • Handle form submission and input validation.
  • Dynamically create and append list items.
  • Toggle completed state and delete tasks.
  • Persist tasks using localStorage.
  • Enhance with editing, filtering, and accessibility.

Building a to-do list app is more than a coding exercise; it’s a practical introduction to real-world programming challenges that prepare you for the journey of web development.

Happy coding!

Read:

Understanding JavaScript Data Types and Variables

How to Work with DOM (Document Object Model) in JavaScript

JavaScript Control Flow: Using If Statements, Loops, and Switch Cases

JavaScript ES6 Features You Should Know About


FAQs

1. What is the purpose of using localStorage in a to-do list app?

localStorage allows the app to save tasks in the browser so that the list persists even after the page is refreshed or the browser is closed, improving user experience.

2. How do you add a new task to the to-do list using JavaScript?

You capture the input from the user, create a new DOM element (like an <li>), set its content, and append it to the task list container dynamically.

3. How can you mark a task as completed in a to-do list app?

By toggling a CSS class (e.g., .completed) on the task element, often triggered by clicking the task, allowing for visual indication like strikethrough text.

4. What event should be handled to add tasks when the user presses the Enter key?

The form’s submit event is handled, which captures the Enter key press when the input is focused, preventing default behavior and adding the new task.

5. How do you prevent a delete button inside a task from triggering the task completion toggle?

Use event.stopPropagation() inside the delete button’s click event handler to prevent the event from bubbling up to the task element’s click listener.

Leave a Comment