Introduction
In this tutorial, we’ll create a fully functional To-Do List application using HTML and JavaScript. This project is perfect for learning fundamental web development concepts including array manipulation, DOM operations, event handling, and local data management.
What we’ll build:
- Add new tasks to the list
- Mark tasks as completed
- Delete individual tasks
- Clear all completed tasks
- Display task counter
Prerequisites: Basic understanding of HTML and JavaScript
Core Concepts We’ll Learn:
• Array methods (push, splice, filter)
• DOM manipulation and creation
• Event handling (click, keypress)
• Conditional rendering
• Function organization
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple To-Do List</title>
</head>
<body>
<h1>My To-Do List</h1>
<input type="text" id="taskInput" placeholder="Enter a task..." onkeypress="if(event.key==='Enter') addTask()">
<button onclick="addTask()">Add Task</button>
<ul id="taskList"></ul>
<script>
let tasks = [];
let taskId = 1;
function addTask() {
const input = document.getElementById('taskInput');
const text = input.value.trim();
if (!text) return;
tasks.push({id: taskId++, text: text, done: false});
input.value = '';
renderTasks();
}
function toggleTask(id) {
const task = tasks.find(t => t.id === id);
if (task) task.done = !task.done;
renderTasks();
}
function deleteTask(id) {
tasks = tasks.filter(t => t.id !== id);
renderTasks();
}
function renderTasks() {
const list = document.getElementById('taskList');
list.innerHTML = tasks.map(task =>
`<li style="${task.done ? 'text-decoration: line-through; opacity: 0.6;' : ''}">
<span onclick="toggleTask(${task.id})" style="cursor: pointer;">
${task.done ? '✅' : '⭕'} ${task.text}
</span>
<button onclick="deleteTask(${task.id})" style="margin-left: 10px;">Delete</button>
</li>`
).join('');
}
</script>
</body>
</html>
Step 1: Setting Up the Basic HTML Structure
Let’s start by creating the foundation of our To-Do List app.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple To-Do List</title>
</head>
<body>
<div class="container">
<h1>My To-Do List</h1>
<!-- Input section for adding new tasks -->
<div class="input-section">
<input type="text" id="taskInput" placeholder="Enter a new task...">
<button onclick="addTask()">Add Task</button>
</div>
<!-- Task display area -->
<div id="taskList"></div>
<!-- Task management controls -->
<div class="controls">
<button onclick="clearCompleted()">Clear Completed</button>
<span id="taskCounter">0 tasks remaining</span>
</div>
</div>
</body>
</html>
What we created:
• Container div: Wraps all content for better organization
• Input section: Text field and button for adding new tasks
• Task display area: Empty div where tasks will be dynamically created
• Controls section: Button to clear completed tasks and a counter display
• Semantic structure: Clear separation of different app sections
Step 2: Initialize JavaScript Variables and Data Structure
Now let’s set up the JavaScript foundation for managing our tasks.
<script>
// Array to store all tasks
let tasks = [];
// Task counter for unique IDs
let taskIdCounter = 1;
// Get references to frequently used DOM elements
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
const taskCounter = document.getElementById('taskCounter');
</script>
Variables explained:
• tasks array: Stores all task objects with their properties (id, text, completed status)
• taskIdCounter: Ensures each task has a unique identifier for easy management
• DOM references: Cached references to avoid repeated getElementById calls
• Global scope: Variables accessible from all functions in our app
Step 3: Creating the Add Task Function
Let’s build the core functionality to add new tasks to our list.
// Function to add a new task
function addTask() {
// Get the task text and remove extra whitespace
const taskText = taskInput.value.trim();
// Validate input - don't add empty tasks
if (taskText === '') {
alert('Please enter a task!');
return;
}
// Create new task object
const newTask = {
id: taskIdCounter++,
text: taskText,
completed: false
};
// Add task to array
tasks.push(newTask);
// Clear input field
taskInput.value = '';
// Update the display
renderTasks();
updateTaskCounter();
}
Function breakdown:
• Input validation: Checks for empty or whitespace-only input
• trim(): Removes leading/trailing whitespace from user input
• Task object: Contains id, text, and completion status
• Array.push(): Adds the new task to the end of tasks array
• UI updates: Clears input and refreshes the display
• Separation of concerns: One function handles adding, other functions handle display
Step 4: Building the Task Rendering Function
This function creates the visual representation of all tasks.
// Function to render all tasks in the DOM
function renderTasks() {
// Clear existing task display
taskList.innerHTML = '';
// Loop through each task and create HTML elements
tasks.forEach(task => {
// Create main task container
const taskItem = document.createElement('div');
taskItem.className = 'task-item';
taskItem.setAttribute('data-id', task.id);
// Apply completed styling if task is done
if (task.completed) {
taskItem.style.textDecoration = 'line-through';
taskItem.style.opacity = '0.6';
}
// Create task content HTML
taskItem.innerHTML = `
<span onclick="toggleTask(${task.id})" style="cursor: pointer;">
${task.completed ? '✅' : '⭕'} ${task.text}
</span>
<button onclick="deleteTask(${task.id})" style="margin-left: 10px; background: red; color: white;">
Delete
</button>
`;
// Add the task item to the task list
taskList.appendChild(taskItem);
});
}
Rendering logic:
• Clear existing content: Prevents duplicate displays
• forEach loop: Iterates through every task in the array
• createElement(): Dynamically creates HTML elements
• data-id attribute: Links DOM element to task data
• Conditional styling: Visual feedback for completed tasks
• Event handlers: Click functions embedded in HTML
• appendChild(): Adds new elements to the DOM
Step 5: Implementing Task Toggle Functionality
Let’s add the ability to mark tasks as completed or incomplete.
// Function to toggle task completion status
function toggleTask(taskId) {
// Find the task in the array by ID
const task = tasks.find(t => t.id === taskId);
// Toggle the completed status
if (task) {
task.completed = !task.completed;
// Update the display
renderTasks();
updateTaskCounter();
}
}
Toggle functionality:
• Array.find(): Locates the specific task object by its ID
• Boolean toggle: Uses NOT operator (!) to flip true/false values
• Conditional check: Ensures task exists before modifying
• State update: Changes the task’s completed property
• Visual refresh: Re-renders tasks to show new status
Step 6: Adding Task Deletion Feature
Now let’s implement the ability to remove individual tasks.
// Function to delete a specific task
function deleteTask(taskId) {
// Find the index of the task to delete
const taskIndex = tasks.findIndex(t => t.id === taskId);
// Remove task from array if found
if (taskIndex !== -1) {
tasks.splice(taskIndex, 1);
// Update the display
renderTasks();
updateTaskCounter();
}
}
Deletion process:
• Array.findIndex(): Finds the position of the task in the array
• splice() method: Removes elements from array (1 element at the found index)
• Index validation: Checks if task was found (-1 means not found)
• Array modification: Permanently removes the task from data structure
• UI synchronization: Updates display to reflect changes
Step 7: Creating Clear Completed Tasks Function
Let’s add functionality to remove all completed tasks at once.
// Function to clear all completed tasks
function clearCompleted() {
// Filter out completed tasks, keep only incomplete ones
tasks = tasks.filter(task => !task.completed);
// Update the display
renderTasks();
updateTaskCounter();
}
Bulk deletion logic:
• Array.filter(): Creates new array with only tasks that meet the condition
• Negation operator (!): Keeps tasks where completed is false
• Array reassignment: Replaces the tasks array with filtered results
• Efficient operation: Removes multiple items in one operation
• Clean interface: Users can quickly clean up finished tasks
Step 8: Building the Task Counter Function
Let’s add a counter to show how many tasks remain incomplete.
// Function to update task counter display
function updateTaskCounter() {
// Count incomplete tasks
const remainingTasks = tasks.filter(task => !task.completed).length;
// Update counter text with proper grammar
const counterText = remainingTasks === 1 ?
'1 task remaining' :
`${remainingTasks} tasks remaining`;
taskCounter.textContent = counterText;
}
Counter functionality:
• Array.filter(): Gets array of incomplete tasks
• length property: Counts how many tasks remain
• Ternary operator: Handles singular vs plural grammar correctly
• Template literals: Embeds the count number in the text
• textContent: Updates the DOM element’s text safely
Step 9: Adding Keyboard Support
Let’s make the app more user-friendly by supporting the Enter key.
// Allow Enter key to add tasks
taskInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
addTask();
}
});
Keyboard enhancement:
• addEventListener(): Attaches event listener to input field
• keypress event: Detects when keys are pressed in the input
• event.key: Identifies which specific key was pressed
• Enter key detection: Calls addTask() when Enter is pressed
• User experience: Allows rapid task entry without mouse clicks
Step 10: Initialize the Application
Finally, let’s set up the initial state of our app.
// Initialize the app when page loads
function initializeApp() {
// Clear any existing content
renderTasks();
updateTaskCounter();
// Focus on input field for immediate use
taskInput.focus();
}
// Run initialization when page loads
window.addEventListener('load', initializeApp);
Initialization process:
• Clear state: Ensures clean starting point
• Initial render: Sets up empty task list display
• Counter setup: Shows “0 tasks remaining”
• Input focus: Cursor ready for immediate typing
• load event: Runs initialization after page fully loads
Complete Code
Here’s the final, complete code for our To-Do List application:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple To-Do List</title>
</head>
<body>
<div class="container">
<h1>My To-Do List</h1>
<!-- Input section for adding new tasks -->
<div class="input-section">
<input type="text" id="taskInput" placeholder="Enter a new task...">
<button onclick="addTask()">Add Task</button>
</div>
<!-- Task display area -->
<div id="taskList"></div>
<!-- Task management controls -->
<div class="controls">
<button onclick="clearCompleted()">Clear Completed</button>
<span id="taskCounter">0 tasks remaining</span>
</div>
</div>
<script>
// Array to store all tasks
let tasks = [];
// Task counter for unique IDs
let taskIdCounter = 1;
// Get references to frequently used DOM elements
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
const taskCounter = document.getElementById('taskCounter');
// Function to add a new task
function addTask() {
// Get the task text and remove extra whitespace
const taskText = taskInput.value.trim();
// Validate input - don't add empty tasks
if (taskText === '') {
alert('Please enter a task!');
return;
}
// Create new task object
const newTask = {
id: taskIdCounter++,
text: taskText,
completed: false
};
// Add task to array
tasks.push(newTask);
// Clear input field
taskInput.value = '';
// Update the display
renderTasks();
updateTaskCounter();
}
// Function to render all tasks in the DOM
function renderTasks() {
// Clear existing task display
taskList.innerHTML = '';
// Loop through each task and create HTML elements
tasks.forEach(task => {
// Create main task container
const taskItem = document.createElement('div');
taskItem.className = 'task-item';
taskItem.setAttribute('data-id', task.id);
// Apply completed styling if task is done
if (task.completed) {
taskItem.style.textDecoration = 'line-through';
taskItem.style.opacity = '0.6';
}
// Create task content HTML
taskItem.innerHTML = `
<span onclick="toggleTask(${task.id})" style="cursor: pointer;">
${task.completed ? '✅' : '⭕'} ${task.text}
</span>
<button onclick="deleteTask(${task.id})" style="margin-left: 10px; background: red; color: white;">
Delete
</button>
`;
// Add the task item to the task list
taskList.appendChild(taskItem);
});
}
// Function to toggle task completion status
function toggleTask(taskId) {
// Find the task in the array by ID
const task = tasks.find(t => t.id === taskId);
// Toggle the completed status
if (task) {
task.completed = !task.completed;
// Update the display
renderTasks();
updateTaskCounter();
}
}
// Function to delete a specific task
function deleteTask(taskId) {
// Find the index of the task to delete
const taskIndex = tasks.findIndex(t => t.id === taskId);
// Remove task from array if found
if (taskIndex !== -1) {
tasks.splice(taskIndex, 1);
// Update the display
renderTasks();
updateTaskCounter();
}
}
// Function to clear all completed tasks
function clearCompleted() {
// Filter out completed tasks, keep only incomplete ones
tasks = tasks.filter(task => !task.completed);
// Update the display
renderTasks();
updateTaskCounter();
}
// Function to update task counter display
function updateTaskCounter() {
// Count incomplete tasks
const remainingTasks = tasks.filter(task => !task.completed).length;
// Update counter text with proper grammar
const counterText = remainingTasks === 1 ?
'1 task remaining' :
`${remainingTasks} tasks remaining`;
taskCounter.textContent = counterText;
}
// Allow Enter key to add tasks
taskInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
addTask();
}
});
// Initialize the app when page loads
function initializeApp() {
// Clear any existing content
renderTasks();
updateTaskCounter();
// Focus on input field for immediate use
taskInput.focus();
}
// Run initialization when page loads
window.addEventListener('load', initializeApp);
</script>
</body>
</html>
Key Programming Concepts Learned
Array Methods: • push(): Adding new elements to the end of an array • find(): Locating specific elements based on conditions • findIndex(): Getting the position of elements in arrays • filter(): Creating new arrays with elements that meet criteria • splice(): Removing elements from specific positions • forEach(): Iterating through all array elements
DOM Manipulation: • getElementById(): Selecting HTML elements by their ID • createElement(): Creating new HTML elements dynamically • appendChild(): Adding elements to the DOM tree • innerHTML: Setting the HTML content of elements • textContent: Safely setting text content of elements
Event Handling: • onclick attributes: Inline event handlers in HTML • addEventListener(): Programmatic event listener attachment • Event object: Accessing information about user interactions • Keyboard events: Responding to key presses
Data Management: • Object creation: Storing related data in structured objects • Unique IDs: Managing individual items in collections • State synchronization: Keeping data and display in sync • Input validation: Ensuring data quality before processing
Testing Your To-Do List
- Save the code as an HTML file (e.g.,
todo-list.html) - Open in browser by double-clicking the file
- Test core features: Add tasks, mark complete, delete tasks
- Try keyboard shortcuts: Use Enter to add tasks
- Test edge cases: Empty input, clearing completed tasks
- Verify counter: Check that remaining tasks count updates correctly

