In this tutorial, I will teach you how to create a Todo List app using HTML5, CSS3, and JavaScript. The complete source code of this JavaScript to-do list app is given below.
You can download the full source code (including images) of this JavaScript todo list app at the end of this article.
HTML5, CSS3, JavaScript Todo List App Source Code
index.html
HTML
x
31
31
1
2
<html lang="en" dir="ltr">
3
<head>
4
<meta charset="utf-8">
5
<title>Todo List App in JavaScript</title>
6
<link rel="stylesheet" href="style.css">
7
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8
<!-- Iconscout Link For Icons -->
9
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.0/css/line.css">
10
</head>
11
<body>
12
<div class="wrapper">
13
<div class="task-input">
14
<img src="bars-icon.svg" alt="icon">
15
<input type="text" placeholder="Add a new task">
16
</div>
17
<div class="controls">
18
<div class="filters">
19
<span class="active" id="all">All</span>
20
<span id="pending">Pending</span>
21
<span id="completed">Completed</span>
22
</div>
23
<button class="clear-btn">Clear All</button>
24
</div>
25
<ul class="task-box"></ul>
26
</div>
27
28
<script src="script.js"></script>
29
30
</body>
31
</html>
script.js
JavaScript
1
106
106
1
const taskInput = document.querySelector(".task-input input"),
2
filters = document.querySelectorAll(".filters span"),
3
clearAll = document.querySelector(".clear-btn"),
4
taskBox = document.querySelector(".task-box");
5
6
let editId,
7
isEditTask = false,
8
todos = JSON.parse(localStorage.getItem("todo-list"));
9
10
filters.forEach(btn => {
11
btn.addEventListener("click", () => {
12
document.querySelector("span.active").classList.remove("active");
13
btn.classList.add("active");
14
showTodo(btn.id);
15
});
16
});
17
18
function showTodo(filter) {
19
let liTag = "";
20
if(todos) {
21
todos.forEach((todo, id) => {
22
let completed = todo.status == "completed" ? "checked" : "";
23
if(filter == todo.status || filter == "all") {
24
liTag += `<li class="task">
25
<label for="${id}">
26
<input onclick="updateStatus(this)" type="checkbox" id="${id}" ${completed}>
27
<p class="${completed}">${todo.name}</p>
28
</label>
29
<div class="settings">
30
<i onclick="showMenu(this)" class="uil uil-ellipsis-h"></i>
31
<ul class="task-menu">
32
<li onclick='editTask(${id}, "${todo.name}")'><i class="uil uil-pen"></i>Edit</li>
33
<li onclick='deleteTask(${id}, "${filter}")'><i class="uil uil-trash"></i>Delete</li>
34
</ul>
35
</div>
36
</li>`;
37
}
38
});
39
}
40
taskBox.innerHTML = liTag || `<span>You don't have any task here</span>`;
41
let checkTask = taskBox.querySelectorAll(".task");
42
!checkTask.length ? clearAll.classList.remove("active") : clearAll.classList.add("active");
43
taskBox.offsetHeight >= 300 ? taskBox.classList.add("overflow") : taskBox.classList.remove("overflow");
44
}
45
showTodo("all");
46
47
function showMenu(selectedTask) {
48
let menuDiv = selectedTask.parentElement.lastElementChild;
49
menuDiv.classList.add("show");
50
document.addEventListener("click", e => {
51
if(e.target.tagName != "I" || e.target != selectedTask) {
52
menuDiv.classList.remove("show");
53
}
54
});
55
}
56
57
function updateStatus(selectedTask) {
58
let taskName = selectedTask.parentElement.lastElementChild;
59
if(selectedTask.checked) {
60
taskName.classList.add("checked");
61
todos[selectedTask.id].status = "completed";
62
} else {
63
taskName.classList.remove("checked");
64
todos[selectedTask.id].status = "pending";
65
}
66
localStorage.setItem("todo-list", JSON.stringify(todos))
67
}
68
69
function editTask(taskId, textName) {
70
editId = taskId;
71
isEditTask = true;
72
taskInput.value = textName;
73
taskInput.focus();
74
taskInput.classList.add("active");
75
}
76
77
function deleteTask(deleteId, filter) {
78
isEditTask = false;
79
todos.splice(deleteId, 1);
80
localStorage.setItem("todo-list", JSON.stringify(todos));
81
showTodo(filter);
82
}
83
84
clearAll.addEventListener("click", () => {
85
isEditTask = false;
86
todos.splice(0, todos.length);
87
localStorage.setItem("todo-list", JSON.stringify(todos));
88
showTodo()
89
});
90
91
taskInput.addEventListener("keyup", e => {
92
let userTask = taskInput.value.trim();
93
if(e.key == "Enter" && userTask) {
94
if(!isEditTask) {
95
todos = !todos ? [] : todos;
96
let taskInfo = {name: userTask, status: "pending"};
97
todos.push(taskInfo);
98
} else {
99
isEditTask = false;
100
todos[editId].name = userTask;
101
}
102
taskInput.value = "";
103
localStorage.setItem("todo-list", JSON.stringify(todos));
104
showTodo(document.querySelector("span.active").id);
105
}
106
});
style.css
CSS
1
217
217
1
/* Import Google Font - Poppins */
2
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
3
*{
4
margin: 0;
5
padding: 0;
6
box-sizing: border-box;
7
font-family: 'Poppins', sans-serif;
8
}
9
body{
10
width: 100%;
11
height: 100vh;
12
overflow: hidden;
13
background: linear-gradient(135deg, #4AB1FF, #2D5CFE);
14
}
15
::selection{
16
color: #fff;
17
background: #3C87FF;
18
}
19
.wrapper{
20
max-width: 405px;
21
padding: 28px 0 30px;
22
margin: 137px auto;
23
background: #fff;
24
border-radius: 7px;
25
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
26
}
27
.task-input{
28
height: 52px;
29
padding: 0 25px;
30
position: relative;
31
}
32
.task-input img{
33
top: 50%;
34
position: absolute;
35
transform: translate(17px, -50%);
36
}
37
.task-input input{
38
height: 100%;
39
width: 100%;
40
outline: none;
41
font-size: 18px;
42
border-radius: 5px;
43
padding: 0 20px 0 53px;
44
border: 1px solid #999;
45
}
46
.task-input input:focus,
47
.task-input input.active{
48
padding-left: 52px;
49
border: 2px solid #3C87FF;
50
}
51
.task-input input::placeholder{
52
color: #bfbfbf;
53
}
54
.controls, li{
55
display: flex;
56
align-items: center;
57
justify-content: space-between;
58
}
59
.controls{
60
padding: 18px 25px;
61
border-bottom: 1px solid #ccc;
62
}
63
.filters span{
64
margin: 0 8px;
65
font-size: 17px;
66
color: #444444;
67
cursor: pointer;
68
}
69
.filters span:first-child{
70
margin-left: 0;
71
}
72
.filters span.active{
73
color: #3C87FF;
74
}
75
.controls .clear-btn{
76
border: none;
77
opacity: 0.6;
78
outline: none;
79
color: #fff;
80
cursor: pointer;
81
font-size: 13px;
82
padding: 7px 13px;
83
border-radius: 4px;
84
letter-spacing: 0.3px;
85
pointer-events: none;
86
transition: transform 0.25s ease;
87
background: linear-gradient(135deg, #1798fb 0%, #2D5CFE 100%);
88
}
89
.clear-btn.active{
90
opacity: 0.9;
91
pointer-events: auto;
92
}
93
.clear-btn:active{
94
transform: scale(0.93);
95
}
96
.task-box{
97
margin-top: 20px;
98
margin-right: 5px;
99
padding: 0 20px 10px 25px;
100
}
101
.task-box.overflow{
102
overflow-y: auto;
103
max-height: 300px;
104
}
105
.task-box::scrollbar{
106
width: 5px;
107
}
108
.task-box::scrollbar-track{
109
background: #f1f1f1;
110
border-radius: 25px;
111
}
112
.task-box::scrollbar-thumb{
113
background: #e6e6e6;
114
border-radius: 25px;
115
}
116
.task-box .task{
117
list-style: none;
118
font-size: 17px;
119
margin-bottom: 18px;
120
padding-bottom: 16px;
121
align-items: flex-start;
122
border-bottom: 1px solid #ccc;
123
}
124
.task-box .task:last-child{
125
margin-bottom: 0;
126
border-bottom: 0;
127
padding-bottom: 0;
128
}
129
.task-box .task label{
130
display: flex;
131
align-items: flex-start;
132
}
133
.task label input{
134
margin-top: 7px;
135
accent-color: #3C87FF;
136
}
137
.task label p{
138
user-select: none;
139
margin-left: 12px;
140
word-wrap: break-word;
141
}
142
.task label p.checked{
143
text-decoration: line-through;
144
}
145
.task-box .settings{
146
position: relative;
147
}
148
.settings :where(i, li){
149
cursor: pointer;
150
}
151
.settings .task-menu{
152
z-index: 10;
153
right: -5px;
154
bottom: -65px;
155
padding: 5px 0;
156
background: #fff;
157
position: absolute;
158
border-radius: 4px;
159
transform: scale(0);
160
transform-origin: top right;
161
box-shadow: 0 0 6px rgba(0,0,0,0.15);
162
transition: transform 0.2s ease;
163
}
164
.task-box .task:last-child .task-menu{
165
bottom: 0;
166
transform-origin: bottom right;
167
}
168
.task-box .task:first-child .task-menu{
169
bottom: -65px;
170
transform-origin: top right;
171
}
172
.task-menu.show{
173
transform: scale(1);
174
}
175
.task-menu li{
176
height: 25px;
177
font-size: 16px;
178
margin-bottom: 2px;
179
padding: 17px 15px;
180
cursor: pointer;
181
justify-content: flex-start;
182
}
183
.task-menu li:last-child{
184
margin-bottom: 0;
185
}
186
.settings li:hover{
187
background: #f5f5f5;
188
}
189
.settings li i{
190
padding-right: 8px;
191
}
192
193
@media (max-width: 400px) {
194
body{
195
padding: 0 10px;
196
}
197
.wrapper {
198
padding: 20px 0;
199
}
200
.filters span{
201
margin: 0 5px;
202
}
203
.task-input{
204
padding: 0 20px;
205
}
206
.controls{
207
padding: 18px 20px;
208
}
209
.task-box{
210
margin-top: 20px;
211
margin-right: 5px;
212
padding: 0 15px 10px 20px;
213
}
214
.task label input{
215
margin-top: 4px;
216
}
217
}