Build A Simple TODO List App With Vanilla JS
Let's dive into creating a simple TODO list app using just vanilla HTML, CSS, and JavaScript. This project is perfect for beginners looking to solidify their understanding of front-end development fundamentals. We'll build a single-file application that allows you to add, mark as complete, and delete tasks, with the added benefit of storing your tasks in localStorage so they persist even after you close the browser tab. This means your to-do list will be ready and waiting for you whenever you return.
Getting Started: The HTML Structure
First things first, let's lay down the foundation with our HTML. We want a clean and semantic structure that will hold our application's elements. For this simple TODO list app, we'll need a main container, an input field for new tasks, a button to add them, and a list where our tasks will be displayed. We'll also include a title for our app to make it clear what it is.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple TODO List</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>My Awesome TODO List</h1>
<div class="input-section">
<input type="text" id="taskInput" placeholder="Add a new task...">
<button id="addTaskBtn">Add Task</button>
</div>
<ul id="taskList">
<!-- Tasks will be added here by JavaScript -->
</ul>
</div>
<script src="script.js"></script>
</body>
</html>
In this HTML, we have a container div to wrap everything. Inside, an h1 for the title, an input-section containing the text input (#taskInput) and the Add Task button (#addTaskBtn). Finally, an unordered list (#taskList) is where our dynamic tasks will live. We've also linked a style.css for our styling and a script.js for our JavaScript logic. This setup is the blueprint for our vanilla JavaScript TODO list app.
Styling Your TODO List: The CSS
Now, let's make our simple TODO list app look presentable. We'll add some basic CSS to style the elements and give it a clean, user-friendly appearance. This part is all about making the interface intuitive and visually appealing. We'll focus on centering the content, styling the input and buttons, and making the task list easy to read.
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
margin: 0;
padding-top: 50px; /* Add some space at the top */
}
.container {
background-color: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 500px;
text-align: center;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.input-section {
display: flex;
margin-bottom: 20px;
gap: 10px;
}
#taskInput {
flex-grow: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #0056b3;
}
#taskList {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
#taskList li {
background-color: #eee;
padding: 12px;
margin-bottom: 10px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: space-between;
word-break: break-word; /* Prevent long words from breaking layout */
}
#taskList li.completed {
text-decoration: line-through;
color: #888;
background-color: #ddd;
}
.task-actions {
display: flex;
gap: 10px;
align-items: center;
}
.task-actions input[type="checkbox"] {
cursor: pointer;
transform: scale(1.2); /* Slightly larger checkbox */
}
.task-actions .delete-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
.task-actions .delete-btn:hover {
background-color: #c82333;
}
This CSS provides a clean look for our simple TODO list app. We've styled the body to center content, the container for a card-like appearance, and the input elements for usability. Crucially, we've defined styles for individual list items (#taskList li) and how they look when completed (.completed), including adding styles for the mark as complete checkbox and the delete task button. This visual polish is key to a good user experience in any web application.
The Brains: JavaScript Logic
Now for the core of our simple TODO list app – the JavaScript! This is where we'll handle adding tasks, marking them complete, deleting them, and most importantly, persisting the data using localStorage. We'll break this down into several key functions.
Core Functionality: Add, Complete, Delete
Let's start by getting references to our HTML elements and setting up event listeners. We need to listen for clicks on the 'Add Task' button and for interactions within the task list itself.
// script.js
document.addEventListener('DOMContentLoaded', () => {
const taskInput = document.getElementById('taskInput');
const addTaskBtn = document.getElementById('addTaskBtn');
const taskList = document.getElementById('taskList');
// --- Function to add a new task ---
const addTask = () => {
const taskText = taskInput.value.trim();
if (taskText === '') return; // Don't add empty tasks
const li = document.createElement('li');
li.textContent = taskText;
// Create checkbox and delete button
const taskActionsDiv = document.createElement('div');
taskActionsDiv.classList.add('task-actions');
const completeCheckbox = document.createElement('input');
completeCheckbox.type = 'checkbox';
completeCheckbox.addEventListener('change', () => {
li.classList.toggle('completed');
saveTasks(); // Save state after toggling completion
});
const deleteBtn = document.createElement('button');
deleteBtn.textContent = 'Delete';
deleteBtn.classList.add('delete-btn');
deleteBtn.addEventListener('click', () => {
taskList.removeChild(li);
saveTasks(); // Save state after deleting
});
taskActionsDiv.appendChild(completeCheckbox);
taskActionsDiv.appendChild(deleteBtn);
li.appendChild(taskActionsDiv); // Add actions to the list item
taskList.appendChild(li);
taskInput.value = ''; // Clear the input field
saveTasks(); // Save the new task
};
// --- Event Listeners ---
addTaskBtn.addEventListener('click', addTask);
// Allow adding task by pressing Enter key
taskInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
addTask();
}
});
// --- Function to render tasks from localStorage ---
const renderTasks = () => {
const tasks = JSON.parse(localStorage.getItem('tasks') || '[]');
tasks.forEach(taskData => {
const li = document.createElement('li');
li.textContent = taskData.text;
if (taskData.completed) {
li.classList.add('completed');
}
const taskActionsDiv = document.createElement('div');
taskActionsDiv.classList.add('task-actions');
const completeCheckbox = document.createElement('input');
completeCheckbox.type = 'checkbox';
completeCheckbox.checked = taskData.completed;
completeCheckbox.addEventListener('change', () => {
li.classList.toggle('completed');
saveTasks();
});
const deleteBtn = document.createElement('button');
deleteBtn.textContent = 'Delete';
deleteBtn.classList.add('delete-btn');
deleteBtn.addEventListener('click', () => {
taskList.removeChild(li);
saveTasks();
});
taskActionsDiv.appendChild(completeCheckbox);
taskActionsDiv.appendChild(deleteBtn);
li.appendChild(taskActionsDiv);
taskList.appendChild(li);
});
};
// --- Function to save tasks to localStorage ---
const saveTasks = () => {
const tasks = [];
taskList.querySelectorAll('li').forEach(li => {
tasks.push({
text: li.firstChild.textContent.trim(), // Get text before the actions div
completed: li.classList.contains('completed')
});
});
localStorage.setItem('tasks', JSON.stringify(tasks));
};
// Initial render of tasks when the page loads
renderTasks();
});
In this JavaScript code, we first wait for the DOM to be fully loaded. We get references to our input field, add button, and task list. The addTask function creates a new list item, sets its text, adds a mark as complete checkbox and a delete task button to it, and appends it to the taskList. It also clears the input and calls saveTasks. The renderTasks function is crucial for loading tasks from localStorage when the app starts, and saveTasks handles updating localStorage whenever a change occurs (add, complete, delete). This implementation ensures that our simple TODO list app remembers your tasks.
Integrating LocalStorage
The localStorage API in JavaScript is a simple way to store key-value pairs in the user's browser. For our simple TODO list app, we'll use it to store an array of task objects. Each object will have a text property and a completed property. The saveTasks function iterates through all the current li elements in the taskList, extracts their text and completion status, and stores them as a JSON string in localStorage under the key 'tasks'. The renderTasks function does the reverse: it retrieves the JSON string, parses it back into an array, and creates the corresponding li elements on the page. It also ensures the checkbox is checked and the completed class is applied if the task was marked as completed.
This persistent storage is what makes our vanilla JavaScript TODO list app feel more robust. Without it, all your added tasks would disappear upon refreshing the page. By using localStorage, the user's to-do list is preserved across sessions, providing a much better user experience and demonstrating a practical application of client-side data storage in a single-file TODO list app.
Enhancements and Next Steps
While this simple TODO list app covers the basic requirements, there are many ways to enhance it. You could add features like editing existing tasks, prioritizing tasks, setting due dates, or even adding categories. For a more advanced application, you might consider using a JavaScript framework like React, Vue, or Angular, which can help manage more complex states and component structures. However, for learning the fundamentals and building a quick, functional tool, this vanilla implementation is excellent.
This project is a great stepping stone into web development. You've learned how to manipulate the DOM, handle user events, and utilize browser storage. Keep experimenting and building!
For more on web development and JavaScript, check out these resources:
- MDN Web Docs: Mozilla Developer Network - An essential resource for all things web development.
- freeCodeCamp: freeCodeCamp.org - Offers comprehensive tutorials and challenges to improve your coding skills.