
In this comprehensive guide, we’ll walk through creating a feature-rich to-do list application using core web technologies: HTML for structure, CSS for styling, and JavaScript for functionality. This project will help you understand fundamental web development concepts while building a practical application. By the end, you’ll have a working to-do list with task management, persistence, and interactive features.
Table of Contents
- Introduction to To-Do List Applications
- Project Features Overview
- Setting Up the HTML Structure
- Styling with CSS
- Core JavaScript Functionality
- Implementing Local Storage
- Enhancing the Application
- Conclusion and Next Steps
1. Introduction to To-Do List Applications
To-do list applications are excellent beginner projects that teach essential web development skills:
- DOM manipulation
- Event handling
- Data persistence
- CRUD operations (Create, Read, Update, Delete)
- Responsive design
Our application will include:
- Adding new tasks
- Marking tasks as complete
- Deleting tasks
- Local storage for data persistence
- Edit functionality
- Task filtering
- Smooth animations
2. Project Features Overview
Before diving into the code, let’s outline our application’s features:
- Task Management
- Add new tasks
- Edit existing tasks
- Delete tasks
- Toggle completion status
- Data Persistence
- Save tasks to localStorage
- Load tasks on page refresh
- User Interface
- Clean, responsive design
- Visual feedback for actions
- Filtering options (All/Active/Completed)
- Animations for task interactions
3. Setting Up the HTML Structure
Create a new index.html
file with this base structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List App</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Todo List</h1>
<div class="input-group">
<input type="text" id="taskInput" placeholder="Enter new task...">
<button id="addTaskBtn">Add Task</button>
</div>
<div class="filter-buttons">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="active">Active</button>
<button class="filter-btn" data-filter="completed">Completed</button>
</div>
<ul id="taskList"></ul>
</div>
<script src="app.js"></script>
</body>
</html>
HTML Breakdown:
- Container for organization
- Input field and button for new tasks
- Filter buttons to view different task states
- Unordered list to display tasks
- External CSS and JavaScript files
4. Styling with CSS
Create styles.css
with these styles:
:root {
--primary-color: #4a90e2;
--secondary-color: #f44336;
--background-color: #f5f5f5;
--text-color: #333;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 2rem auto;
padding: 1.5rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
margin-bottom: 1.5rem;
color: var(--primary-color);
}
.input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
#taskInput {
flex: 1;
padding: 0.8rem;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
#addTaskBtn {
padding: 0.8rem 1.5rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
#addTaskBtn:hover {
background-color: #357abd;
}
.filter-buttons {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.filter-btn {
flex: 1;
padding: 0.5rem;
background-color: #eee;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.filter-btn.active {
background-color: var(--primary-color);
color: white;
}
#taskList {
list-style: none;
}
.task-item {
display: flex;
align-items: center;
padding: 0.8rem;
margin-bottom: 0.5rem;
background-color: #f8f8f8;
border-radius: 4px;
transition: all 0.3s;
}
.task-item:hover {
transform: translateX(5px);
box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
}
.task-item.completed {
background-color: #e8e8e8;
opacity: 0.7;
}
.task-item.completed .task-text {
text-decoration: line-through;
}
.task-text {
flex: 1;
margin: 0 1rem;
}
.task-actions {
display: flex;
gap: 0.5rem;
}
.edit-btn, .delete-btn {
padding: 0.4rem 0.8rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.3s;
}
.edit-btn {
background-color: #ffc107;
color: white;
}
.delete-btn {
background-color: var(--secondary-color);
color: white;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translateX(0); }
to { opacity: 0; transform: translateX(100px); }
}
.fade-in {
animation: fadeIn 0.3s ease-out;
}
.fade-out {
animation: fadeOut 0.3s ease-in forwards;
}
CSS Highlights:
- Modern box-shadow and transitions for depth
- Responsive layout with flexbox
- State-based styling (completed tasks)
- Hover effects for better UX
- Animations for task additions/removals
- CSS variables for easy theming
5. Core JavaScript Functionality
Create app.js
with this initial code:
class TodoList {
constructor() {
this.tasks = JSON.parse(localStorage.getItem('tasks')) || [];
this.taskInput = document.getElementById('taskInput');
this.addTaskBtn = document.getElementById('addTaskBtn');
this.taskList = document.getElementById('taskList');
this.filterButtons = document.querySelectorAll('.filter-btn');
this.currentFilter = 'all';
this.initializeEventListeners();
this.renderTasks();
}
initializeEventListeners() {
this.addTaskBtn.addEventListener('click', () => this.addTask());
this.taskInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.addTask();
});
this.taskList.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
this.deleteTask(e.target.closest('.task-item').dataset.id);
}
if (e.target.classList.contains('edit-btn')) {
this.editTask(e.target.closest('.task-item').dataset.id);
}
if (e.target.type === 'checkbox') {
this.toggleTask(e.target.closest('.task-item').dataset.id);
}
});
this.filterButtons.forEach(button => {
button.addEventListener('click', () => this.filterTasks(button.dataset.filter));
});
}
addTask() {
const taskText = this.taskInput.value.trim();
if (!taskText) return;
const newTask = {
id: Date.now(),
text: taskText,
completed: false
};
this.tasks.push(newTask);
this.saveTasks();
this.renderTasks();
this.taskInput.value = '';
}
deleteTask(taskId) {
this.tasks = this.tasks.filter(task => task.id !== Number(taskId));
this.saveTasks();
this.renderTasks();
}
toggleTask(taskId) {
this.tasks = this.tasks.map(task =>
task.id === Number(taskId) ? {...task, completed: !task.completed} : task
);
this.saveTasks();
this.renderTasks();
}
editTask(taskId) {
const task = this.tasks.find(task => task.id === Number(taskId));
const newText = prompt('Edit task:', task.text);
if (newText !== null) {
task.text = newText.trim();
this.saveTasks();
this.renderTasks();
}
}
filterTasks(filter) {
this.currentFilter = filter;
this.filterButtons.forEach(btn =>
btn.classList.toggle('active', btn.dataset.filter === filter)
);
this.renderTasks();
}
saveTasks() {
localStorage.setItem('tasks', JSON.stringify(this.tasks));
}
renderTasks() {
this.taskList.innerHTML = '';
const filteredTasks = this.tasks.filter(task => {
if (this.currentFilter === 'active') return !task.completed;
if (this.currentFilter === 'completed') return task.completed;
return true;
});
filteredTasks.forEach(task => {
const taskElement = document.createElement('li');
taskElement.className = `task-item fade-in ${task.completed ? 'completed' : ''}`;
taskElement.dataset.id = task.id;
taskElement.innerHTML = `
<input type="checkbox" ${task.completed ? 'checked' : ''}>
<span class="task-text">${task.text}</span>
<div class="task-actions">
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</div>
`;
this.taskList.appendChild(taskElement);
});
}
}
// Initialize application
const todoApp = new TodoList();
JavaScript Breakdown:
TodoList
class encapsulates functionality- Constructor initializes state and DOM references
- Event delegation for dynamic elements
- Array methods for task manipulation
- LocalStorage integration
- Filtering logic
- ES6 features like arrow functions and template literals
6. Implementing Local Storage
Our application uses the browser’s localStorage
to persist data:
// In constructor
this.tasks = JSON.parse(localStorage.getItem('tasks')) || [];
// Save method
saveTasks() {
localStorage.setItem('tasks', JSON.stringify(this.tasks));
}
Key Points:
- Data persists even after browser restart
- Maximum storage limit of ~5MB
- Data stored as strings (requires JSON conversion)
- Synchronous operation (consider IndexedDB for larger datasets)
7. Enhancing the Application
Let’s add more features to improve functionality:
7.1 Task Editing with Inline Input
Modify the editTask
method:
editTask(taskId) {
const taskItem = document.querySelector(`[data-id="${taskId}"]`);
const taskText = taskItem.querySelector('.task-text');
const currentText = taskText.textContent;
const input = document.createElement('input');
input.type = 'text';
input.value = currentText;
input.className = 'edit-input';
taskText.replaceWith(input);
input.focus();
const saveEdit = () => {
const newText = input.value.trim();
if (newText) {
const task = this.tasks.find(t => t.id === Number(taskId));
task.text = newText;
this.saveTasks();
this.renderTasks();
}
};
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') saveEdit();
});
input.addEventListener('blur', saveEdit);
}
7.2 Improved Animations
Add these CSS transitions:
.task-item {
transition: all 0.3s ease-out;
}
.task-item.fade-out {
opacity: 0;
transform: translateX(100%);
}
Update the delete method:
deleteTask(taskId) {
const taskItem = document.querySelector(`[data-id="${taskId}"]`);
taskItem.classList.add('fade-out');
setTimeout(() => {
this.tasks = this.tasks.filter(task => task.id !== Number(taskId));
this.saveTasks();
this.renderTasks();
}, 300);
}
7.3 Character Counter
Add to HTML:
<div class="char-counter">
<span id="charCount">0</span>/140
</div>
Add to JavaScript:
updateCharCounter() {
const count = this.taskInput.value.length;
document.getElementById('charCount').textContent = count;
if (count > 140) {
this.taskInput.classList.add('error');
} else {
this.taskInput.classList.remove('error');
}
}
// Add event listener
this.taskInput.addEventListener('input', () => this.updateCharCounter());
8. Conclusion and Next Steps
You’ve now built a full-featured to-do list application with:
- Task management
- Local storage
- Filtering
- Animations
- Edit functionality
Next Enhancement Ideas:
- Due dates and priorities
- Drag-and-drop reordering
- Categories/tags
- Server synchronization
- User authentication
- Dark mode toggle
This project demonstrates core web development concepts and provides a foundation for building more complex applications. Continue experimenting with features and refining the UI to deepen your understanding of front-end development.
Final Code Availability:
All code files are available on [GitHub Repository Link]
Leave a Reply