How to Build a To-Do List App in JavaScript: A Beginner's Guide (Free Source Code)
Learn to build a complete to-do list app in JavaScript from scratch β HTML structure, CSS styling, interactive features, and localStorage persistence. Includes a free downloadable source code with step-by-step tutorial for beginners.
Every developer remembers their first "real" JavaScript project. For most of us, it wasn't a flashy clone of Instagram or a complex dashboard β it was a humble to-do list app. And there's a reason for that: a to-do list teaches you almost every fundamental concept in modern web development while giving you something genuinely useful at the end.
In this guide, you'll build a complete task-manager app with HTML, CSS, and vanilla JavaScript β the same trio that powers the free Todo List App source code on CodeFree. Your tasks will be saved in the browser with localStorage, so nothing is lost when you refresh the page. By the end, you'll have a working app you built yourself, plus a free download you can study and reuse.
Why a To-Do List App Is the Perfect First JavaScript Project
Beginners often ask: "Should I start with a calculator, a quiz app, or a to-do list?" Here's why the to-do list wins every time:
- It covers CRUD fundamentals. Create (add a task), Read (display tasks), Update (mark complete or edit), Delete (remove a task) β these four operations are the backbone of almost every web app, from e-commerce carts to admin dashboards.
- It teaches DOM manipulation. You'll learn how JavaScript reads input, creates elements, and updates the page in real time β without any frameworks.
- It introduces browser storage. With localStorage, your app becomes persistent. That's a genuine "wow" moment for a beginner: close the tab, come back tomorrow, and your tasks are still there.
- It has visible progress. Unlike abstract exercises, you can see your app working within the first 20 minutes. That motivation matters β especially for students in India learning on a tight schedule between college and internships.
You don't need Node.js, npm, or any build tools. A text editor and a browser are enough. If you can run VS Code and open a file in Chrome, you're ready.
What You'll Learn (and What You Need)
By the end of this tutorial, you will be able to:
- Structure a page with semantic HTML (forms, inputs, lists)
- Style an app with modern CSS (flexbox, responsive layout, dark-friendly colors)
- Handle user events with JavaScript (clicks, form submits, key presses)
- Render lists dynamically with
document.createElementand template strings - Save and load data with
localStorageusingJSON.stringify/JSON.parse - Prevent common beginner bugs (empty submissions, XSS via innerHTML)
Prerequisites: basic familiarity with HTML tags and CSS selectors. You don't need to know any JavaScript yet β we'll explain every concept as it appears. Estimated time: 1β2 hours.
Step 1 β Setting Up the HTML Skeleton
Every web app starts with HTML. Our to-do app needs three things: an input box for new tasks, a button to add them, and a list to display them. Keep the markup simple and semantic:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My To-Do List</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="app">
<h1>My Tasks</h1>
<form id="task-form">
<input id="task-input" type="text"
placeholder="What needs to be done?" autocomplete="off">
<button type="submit">Add</button>
</form>
<div class="filters">
<button data-filter="all" class="active">All</button>
<button data-filter="active">Active</button>
<button data-filter="completed">Completed</button>
</div>
<ul id="task-list"></ul>
<p id="counter">0 tasks left</p>
</main>
<script src="app.js"></script>
</body>
</html>
Notice we wrapped the input in a <form>. That's deliberate: it lets users press Enter to add a task, which feels natural, and we can handle the form's submit event in JavaScript. The filter buttons (All / Active / Completed) are optional polish β they teach you state management at a beginner level, and they make your app look genuinely professional.
Step 2 β Styling with CSS That Looks Professional
Beginner projects often look dated because they skip styling. You don't need to be a designer β a clean card layout, good spacing, and one accent color go a long way. Here's a compact starting point:
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f1f5f9;
min-height: 100vh;
display: flex;
justify-content: center;
padding: 2rem 1rem;
}
.app {
width: 100%;
max-width: 520px;
background: #ffffff;
border-radius: 16px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
}
#task-form { display: flex; gap: 0.5rem; margin: 1.25rem 0; }
#task-input {
flex: 1;
padding: 0.75rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 10px;
font-size: 1rem;
}
#task-form button {
padding: 0.75rem 1.25rem;
border: none;
border-radius: 10px;
background: #2563eb;
color: #fff;
font-size: 1rem;
cursor: pointer;
}
#task-list li {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 0;
border-bottom: 1px solid #f1f5f9;
list-style: none;
}
#task-list li.done span { text-decoration: line-through; color: #94a3b8; }
Tip: Use system-ui font stacks instead of importing web fonts for your first projects β it loads instantly and looks native on every device, from a βΉ8,000 Android phone to a MacBook. Once the app works, experiment with Google Fonts, dark mode toggles, or subtle animations. Styling is the easiest way to make a portfolio project stand out in interviews.
Step 3 β Adding Interactivity with JavaScript
This is where the magic happens. Our JavaScript needs to: (1) add a task when the form is submitted, (2) toggle a task as complete when clicked, and (3) delete a task when the delete button is pressed. We'll store tasks in a plain array of objects:
const form = document.getElementById('task-form');
const input = document.getElementById('task-input');
const list = document.getElementById('task-list');
let tasks = [];
let filter = 'all';
// Add a new task
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return; // ignore empty submissions
tasks.push({ id: Date.now(), text, done: false });
input.value = '';
saveTasks();
render();
});
// Toggle complete / delete via event delegation
list.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li) return;
const id = Number(li.dataset.id);
if (e.target.classList.contains('delete-btn')) {
tasks = tasks.filter(t => t.id !== id);
} else {
const task = tasks.find(t => t.id === id);
task.done = !task.done;
}
saveTasks();
render();
});
function render() {
list.innerHTML = '';
const visible = tasks.filter(t =>
filter === 'all' ? true :
filter === 'active' ? !t.done : t.done
);
visible.forEach(t => {
const li = document.createElement('li');
li.dataset.id = t.id;
if (t.done) li.classList.add('done');
const span = document.createElement('span');
span.textContent = t.text; // textContent = XSS-safe!
const del = document.createElement('button');
del.className = 'delete-btn';
del.textContent = 'β';
li.append(span, del);
list.appendChild(li);
});
const left = tasks.filter(t => !t.done).length;
document.getElementById('counter').textContent =
`${left} task${left === 1 ? '' : 's'} left`;
}
Two beginner-friendly techniques are worth highlighting here. Event delegation means we attach one click listener to the whole list instead of one per task β simpler and faster. And we use textContent instead of innerHTML when inserting task text, which prevents cross-site scripting (XSS): if a user types <script> as a task, it shows as harmless text instead of executing. These small habits separate hobby code from professional code.
Step 4 β Saving Tasks with localStorage (Persistence)
Right now, refreshing the page wipes your tasks β because the tasks array lives only in memory. localStorage fixes that: it's a tiny key-value store built into every browser that survives page reloads and even browser restarts. Two functions are all you need:
// Save tasks to the browser
function saveTasks() {
localStorage.setItem('my-tasks', JSON.stringify(tasks));
}
// Load tasks when the page opens
function loadTasks() {
try {
const saved = localStorage.getItem('my-tasks');
if (saved) tasks = JSON.parse(saved);
} catch {
tasks = []; // corrupted data? start fresh
}
render();
}
loadTasks(); // run on page load
localStorage only stores strings, which is why we wrap the array with JSON.stringify when saving and JSON.parse when loading. The try/catch is defensive coding β if stored data gets corrupted, the app resets gracefully instead of crashing. That one habit will save you hours of debugging later.
Know the limits: localStorage holds roughly 5MB and is per-browser, per-device β tasks saved on your laptop won't appear on your phone. For a learning project that's perfect; when you're ready for real multi-device sync, the next step is a backend (PHP + MySQL or Firebase) with user accounts.
Common Mistakes Beginners Make (and How to Fix Them)
- Forgetting
e.preventDefault()on form submit. Without it, the browser reloads the page on submit and your JavaScript state is wiped. This is the #1 "my app doesn't work" bug in beginner forums. - Using
innerHTMLwith user input. As mentioned above, always usetextContent(orcreateTextNode) for user-supplied text. It's a security habit that interviewers love to hear about. - Saving to localStorage but never loading on startup. Both directions are required. A common pattern: call
loadTasks()once at the bottom of your script. - Using the task text as the ID. Two tasks with the same text will break toggle/delete logic. Use a unique ID like
Date.now()(or a counter) as shown above. - Trying to learn a framework first. React and Vue are great, but they hide exactly the DOM fundamentals that this project teaches. Build this in vanilla JavaScript first β frameworks will feel easy afterwards.
Frequently Asked Questions
Do I need a server or hosting to build this to-do app?
No. Everything in this guide runs as static files β open index.html in your browser and it works. localStorage needs no server. When you want to share it online, any free static host works: GitHub Pages, Netlify, Vercel, or a shared hosting plan.
Can I use this project in my portfolio or resume?
Absolutely β that's the point. Host it on GitHub Pages, write a short README explaining the features (add/delete/filter, localStorage persistence, XSS-safe rendering), and link it on your resume. For students in India, a clean vanilla-JS project often impresses more in interviews than a copied React tutorial.
How is localStorage different from cookies?
localStorage stores ~5MB per origin, never gets sent to servers automatically, and has a simple synchronous API. Cookies are tiny (~4KB), get sent with every HTTP request, and are better for authentication tokens. For client-side app data like tasks, localStorage is the right tool.
Where can I download the complete source code?
You can download the complete, free Todo List App source code here β it includes everything from this guide (add/complete/delete, All/Active/Completed filters, task counter, localStorage persistence, XSS-safe rendering) in clean index.html, style.css, and app.js files with a README. Study it, modify it, use it in your projects β no attribution required.
Keep Building: Your Next Steps
Congratulations β you've built a real, working web app with persistence, filtering, and safe rendering. But don't stop here. Try extending it: add an edit task feature, a due date with a "due today" highlight, drag-to-reorder with the HTML5 drag-and-drop API, or a dark mode toggle. Each addition teaches you something new.
And if you'd rather study a finished, polished version first, grab the free source code: Todo List App β Task Manager with localStorage. Download it, open it in VS Code, change the colors, break things, fix them β that's how developers are made. Happy coding!
Keep reading
How to Increase Typing Speed: Free WPM & Accuracy Test (2026 Guide)
Know your typing speed in 60 seconds β then use 7 proven techniques to type faster with a free WPM & accuracy test. Built for Indian students, developers & job seekers.
Read article
Free GST Invoice Generator for Indian Small Businesses (2026 Guide)
Learn what makes a GST-compliant tax invoice in India, avoid common mistakes, and download a free PHP invoice generator to create professional invoices in seconds.
Read article