DEV Community

Cover image for How I Built a To Do App with JavaScript — Features, Challenges & What's Next
Hariharan S J
Hariharan S J

Posted on

How I Built a To Do App with JavaScript — Features, Challenges & What's Next

1.Introduction

Every developer has that one project they keep coming back to — the classic Todo App. It sounds simple on the surface, but building one from scratch teaches you a surprising amount about DOM manipulation, state management, browser storage, and UI design. In this blog, I'll walk you through how I built my own Todo App using pure HTML, CSS, and JavaScript — no frameworks, no libraries (except a little confetti surprise). I'll talk about the features I built, the decisions I made along the way, and the improvements I plan to add in the future.

2.Why a Todo App?

Before diving in, you might be wondering — why another Todo App? The honest answer is that it's one of the best exercises for a frontend developer. A Todo App forces you to think about:

  • How to add, update, and remove items dynamically in the DOM

  • How to persist data so it survives a page refresh

  • How to track state and reflect it visually in real time

  • How to style a UI that actually feels good to use

It's the perfect project to solidify your core JavaScript skills before jumping into frameworks like React or Vue.

3.Tech Stack

I intentionally kept this project framework-free to focus on the fundamentals:

  • HTML5 — Semantic structure and layout

  • CSS3 — Custom dark theme using CSS variables, flexbox, and smooth transitions

  • JavaScript — All the logic: DOM manipulation, event handling, and localStorage

  • @hiseb/confetti — A lightweight CDN library for the celebration animation

No build tools, no npm install, no webpack. Just three files and a browser.

4.Features I Built

Adding Tasks

The core feature — a text input and a + button. When the user types a task and clicks the button (or submits the form), the task gets pushed into a tasks array as an object { text, completed: false }, and the list re-renders.

const addTask = () => {
    const taskInput = document.getElementById('taskInput');
    const text = taskInput.value.trim();
    if (text) {
        tasks.push({ text: text, completed: false });
        updateTasksList();
        updateStats();
        saveTasks();
        taskInput.value = "";
    }
};
Enter fullscreen mode Exit fullscreen mode

I used .trim() to avoid saving empty or whitespace-only tasks — a small but important detail.

Completing Tasks (Toggle)

Each task has a checkbox. When checked, it toggles the completed boolean on the task object and re-renders the list. Completed tasks display with a teal strikethrough to visually distinguish them from pending ones.

const toggleTastComplete = (index) => {
    tasks[index].completed = !tasks[index].completed;
    updateTasksList();
    updateStats();
    saveTasks();
};
Enter fullscreen mode Exit fullscreen mode

The CSS handles the visual:

.completed p {
    text-decoration: line-through;
    color: var(--teal);
}
Enter fullscreen mode Exit fullscreen mode

Editing Tasks

Clicking the edit icon on any task loads its text back into the input field and removes it from the list. This lets the user modify it and re-add it with the + button. It's a simple but effective pattern.

const editTask = (index) => {
    const taskInput = document.getElementById('taskInput');
    taskInput.value = tasks[index].text;
    tasks.splice(index, 1);
    updateTasksList();
    updateStats();
    saveTasks();
};
Enter fullscreen mode Exit fullscreen mode

Deleting Tasks

A delete icon removes a task permanently from the array using splice(). The list and stats update immediately.

Progress Bar & Stats Counter

This was one of my favourite parts to build. There's a live progress bar that fills up as you complete tasks, and a circular stats counter showing completed / total.

const updateStats = () => {
    const completeTasks = tasks.filter(task => task.completed).length;
    const totalTasks = tasks.length;
    const progress = (completeTasks / totalTasks) * 100;
    document.getElementById('progress').style.width = `${progress}%`;
    document.getElementById('numbers').innerText = `${completeTasks} / ${totalTasks}`;
};
Enter fullscreen mode Exit fullscreen mode

The progress bar uses a CSS transition for a smooth animation:

#progress {
    transition: all 0.3s ease;
}
Enter fullscreen mode Exit fullscreen mode

Confetti Celebration

My favourite Easter egg — when every single task is checked off, the app fires a confetti animation using the @hiseb/confetti CDN library. It's a small touch that makes the app feel rewarding to use.

if (tasks.length && completeTasks === totalTasks) {
    blaskConfetti();
}
Enter fullscreen mode Exit fullscreen mode

Persistent Storage with localStorage

Tasks are saved to the browser's localStorage so they survive a page refresh. Every time the user adds, edits, deletes, or completes a task, the updated array is serialized and saved.

const saveTasks = () => {
    localStorage.setItem('tasks', JSON.stringify(tasks));
};
Enter fullscreen mode Exit fullscreen mode

On page load, tasks are retrieved and the UI is rebuilt:

document.addEventListener('DOMContentLoaded', function () {
    const storedTasks = JSON.parse(localStorage.getItem('tasks'));
    if (storedTasks) {
        storedTasks.forEach((task) => tasks.push(task));
        updateTasksList();
        updateStats();
    }
});
Enter fullscreen mode Exit fullscreen mode

UI Design Decisions

I went with a deep navy dark theme that feels clean and modern. The colour palette is defined with CSS variables, making it easy to adjust globally:

:root {
    --background: #000430;
    --secondaryBackground: #171c48;
    --text: #fff;
    --purple: #828dff;
    --teal: #24feee;
}
Enter fullscreen mode Exit fullscreen mode

The purple is used for borders, buttons, and the stats circle. The teal is reserved for active/completed states and the progress bar — creating a clear visual hierarchy. The rounded corners and consistent padding make everything feel polished without overcomplicating the CSS.

5.Challenges I Faced

DOM Re-rendering

Every time a task is added, deleted, edited, or toggled, I clear the entire innerHTML of the task list and re-render everything from scratch. This is simple and effective for small lists, but not efficient for larger ones (more on this in future improvements).

Inline Event Handlers

Since task items are built with innerHTML, I used inline onclick attributes to attach event handlers. This works fine but isn't ideal — a better approach would be event delegation on the parent list element.

Scope Issue

There's a subtle bug in the original code — the stats calculation at the top of the file runs before any tasks exist, so it always computes 0 / 0. Moving all initial logic inside the DOMContentLoaded handler or inside updateStats() (which is already correctly placed) would fix this cleanly.

6.What I Learned

  • localStorage is incredibly easy to use and a great first step into data persistence before touching databases.

  • CSS variables make theming clean and maintainable — I'll use them in every project going forward.

  • State management in JS is about maintaining a single source of truth (the tasks array) and always rendering from it, rather than trying to read state from the DOM.

  • Small UX details — like transitions, strikethroughs, and confetti — make a huge difference in how a project feels to use.

7.Future Improvements

There's a lot I'd love to add to make this app more production-ready:

  • Due dates — Add an optional date picker to each task

  • Priority levels — Mark tasks as Low, Medium, or High priority with colour coding

  • Task categories / tags — Organise tasks into groups like Work, Personal, Shopping

  • Backend + user accounts — Move from localStorage to a real database so tasks sync across devices

  • Recurring tasks — Set tasks to repeat daily, weekly, or monthly

You Can Check My To Do List By Clicking The Below Link
To Do List

8.Final Thoughts

Building this Todo App was a great exercise in keeping things simple while still producing something genuinely useful.JavaScript gets a lot less credit than it deserves — you don't always need a framework to build something clean and functional

Top comments (0)