How to build todo app using javascript

In this tutorial we’re going to learn how to create a cool todo app using html css and javascript with the dark light mode functionality

🔗Prerequisites

Prerequisites

🔗Get started ✌

The todo app we’ll build in this tutorial will be pretty cool. a user can add or remove todo by filling todo form and delete todo by clicking delete icon , and mark todo complete by clicking circle

I’ll explain how to build each feature, but you must follow along by typing the code and running it on your end to get the most out of this tutorial

Click here to get starter files

🔗First let’s create the skeleton of the ui

Add font awesome cdn 👇

We’ll use font awesome icons in this project for that we have to include font awesome cdn in body tag you can create your own font awesome kit

js
<script
src="https://kit.fontawesome.com/dd8c49730d.js"
crossorigin="anonymous"
></script>

Markup of todo list , default todo , add todo form 👇

Note:- We creating this todo just for demo purpose we gonna use this markup in javascript while we creating functionality for rendering new todo in todo list with the same ui then we’ll remove this default todo from the todo list
html
<div class="container">
<ul class="todo-list js-todo-list">
<li class="todo-item" data-key="123456">
<input id="123456" type="checkbox" />
<label for="123456" class="tick js-tick"></label>
<span>Workout</span>
<button class="delete-todo js-delete-todo">
<button class="delete-todo js-delete-todo">
<svg fill="var(--svgcolor)" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
</button>
</li>
</ul>
<form class="formselect">
<input autofocus type="text" class="inputselect" placeholder="Eg: Workout">
</form>
</div>

output of above code

output of above code

🔗Let’s add styling into ui

Basic styling

css
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
line-height: 1.4;
background-color: #fff;
transition: all 0.5s;
}

Adding styling into container, svg button , todo list , todo item, todo item span , checked button

css
.container {
width: 100%;
max-width: 700px;
margin: 0 auto;
padding-left: 10px;
padding-right: 10px;
color: #333;
margin: auto;
margin-top: 10%;
}
.todo-list {
list-style: none;
margin-bottom: 20px;
}
.todo-item {
margin-bottom: 10px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.todo-item span {
flex-grow: 1;
margin-left: 10px;
margin-right: 10px;
font-size: 22px;
}
input[type="checkbox"] {
display: none;
}
.tick {
width: 30px;
height: 30px;
border: 3px solid #333;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
Adding styling into container, svg button , todo list , todo item, todo item span , checked button

Adding styling into delete todo button

css
delete-todo {
border: none;
background-color: transparent;
outline: none;
cursor: pointer;
}
.delete-todo svg {
width: 30px;
height: 30px;
pointer-events: none;
}
Adding styling into delete todo button

Adding styling into add todo form

css
form {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
}
input[type="text"] {
width: 100%;
padding: 10px;
border-radius: 4px;
border: 3px solid #333;
}
input:focus {
outline: none;
background-color: #d4e7d5;
}
Adding styling into add todo form

Styling for todo complete effect checked ( we gonna use this in javascript )

css
.done span {
text-decoration: line-through;
user-select: none;
}
.tick::before {
content: "✔️";
font-size: 20px;
display: none;
}
.done .tick::before {
display: inline;
}

Styling done 🎨

See the complete code at the end of this step

🔗Let’s start building feature to add new todo and render todo in the list , store todo as a array object to localstorage

Function for adding new todo

js
function addTodo(todoText, id) {
const todoobject = {
todoText: document.querySelector(".inputselect").value,
checked: false;
id: Date.now(),
};
console.log(todoobject);
}

In above code we create a function for adding a new todo

  • We created todoobject to store all todo detail as an object property
  • In todoobject we getting a value of entered input values with their id by using .value method , creating checked property and giving boolean value false and creating a id in todoobject using date.now method
  • Console logged for demo purpose

Add a event listener submit to the form

js
const form = document.querySelector(".formselect");
form.addEventListener("submit", (event) => {
event.preventDefault();
addTodo();
form.reset();
});

In above code we added a event listener Submit to the form when form is submitted

  1. We will call our addTodo() function for adding todo
  2. We using reset() method to reset form after submitting a todo

See the output in console log 👇

As you can see above video after submitting a form by pressing enter we getting a new todo as an object in the console log

But we want to render a todo in todo-list to display in page to achieve that we have to create a renderTodo() function above addTodo() function

Function for rendering new todo in display

  1. In this function we’re going to pass todo as a parameter to access todo details from todoobject eg: todo.todoText , todo.id , todo.checked
  2. We’re going to select list where we will appending a all new todo items
  3. Select the current todo item in the DOM Check if checked is true add done class effect otherwise as it is
  4. Creating new element list (we’re going to use markup of todo what we created at beginning for demo purpose ) and append node in list
js
function renderTodo(todo) {}

Select todo list for appending all todos in list

js
function renderTodo(todo) {
const list = document.querySelector(".todo-list");
}

Select the current todo item in the DOM ( We’ll use item for deleting todo from todo list later in this tutorial )

js
function renderTodo(todo) {
const list = document.querySelector(".todo-list");
const item = document.querySelector(`[data-key='${todo.id}']`);
}

Check if checked is true add done class effect otherwise as it is

js
function renderTodo(todo) {
const list = document.querySelector(".todo-list");
const item = document.querySelector(`[data-key='${todo.id}']`);
const isChecked = todo.checked ? "done" : "";
}

Create new element li for todo ui

  1. Set the class and data-key attribute
  2. Add html markup of todo
js
function renderTodo(todo) {
const list = document.querySelector(".todo-list");
const item = document.querySelector(`[data-key='${todo.id}']`);
const isChecked = todo.checked ? "done" : "";
const newlist = document.createElement("li");
newlist.setAttribute("class", `todo-item ${isChecked}`);
newlist.setAttribute("data-key", todo.id);
}

We have to just cut and copy markup of todo ui to set the newList.innerHTML what we create at beginning for demo purpose

js
function renderTodo(todo) {
const list = document.querySelector(".todo-list");
const item = document.querySelector(`[data-key='${todo.id}']`);
const isChecked = todo.checked ? "done" : "";
const newlist = document.createElement("li");
newlist.setAttribute("class", `todo-item ${isChecked}`);
newlist.setAttribute("data-key", todo.id);
newlist.innerHTML = `
<input id="${todo.id}" type="checkbox"/>
<label for "${todo.id}" class="tick js-tick"></label>
<span>${todo.todoText}</span>
<button class="delete-todo js-delete-todo">
<button class="delete-todo js-delete-todo">
<svg fill="var(--svgcolor)" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
</button>
`;
// appending a newList in list
list.append(newlist);
}

Yes here we successfully created our function for rendering a new todo in todo list But as you can see we passed todo as a parameter of function to access the property of todoobject for displaying in todo ui

To archive that we have to call renderTodo() function in addTodo function and pass todoobject object as an argument in renderTodo function

Updated addTodo as follows

js
function addTodo(todoText, id) {
const todoobject = {
todoText: document.querySelector(".inputselect").value,
checked: false,
id: Date.now(),
};
console.log(todoobject); // console log for demo purpose
renderTodo(todoobject);
}

In above code we called renderTodo function and passed todoobject as an argument now we can use property of todoobject in renderTodo() function

Now we can remove a default todo from list what we created at beginning for demo purpose update markup of todo list as follows

html
<div class="container">
<ul class="todo-list js-todo-list">
<!-- default todo removed -->
<!-- default todo removed -->
</ul>
<form class="formselect">
<input
autofocus
type="text"
class="inputselect"
placeholder="Eg: Workout"
/>
</form>
</div>

As you can see above it’s work as expected

But here we have one problem when we refresh the page all todos cleared

See below video

To keep it display we have to store data in localstorage for that we can use javascript localStorage object

Save todo data in browser local storage

  1. Create empty todo array for storing all todos as a array object in localstorage
  2. To store todo in localstorage we have to set todo array to localstorage
  3. Push new todoobject to todoArray after calling addTodo()
  4. Get todo array from localstorage to display todos in todo list

Creating empty todoArray array object

js
let todoArray = [];

Set todoArray to localstorage

We can set item to localstorage using localStorage.setItem method in renderTodo()

Add below code line in above renderTodo function

js
localStorage.setItem("todoArray", JSON.stringify(todoArray));

Push new todoobject to todoArray array object

We have to push every new todoobject to todoArray array object after adding todo

Add below code line within addTodo

js
todoArray.push(todoobject);

Get todoArray from localstorage to display todos in todo list

To display todo in todo list we have to get the todo from todoArray after refreshing page to archive that we have to add event listener DOMContentLoaded and get the todoArray from localStorage

js
document.addEventListener("DOMContentLoaded", () => {
const ref = localStorage.getItem("todoArray");
if (ref) {
todoArray = JSON.parse(ref);
todoArray.forEach((t) => {
renderTodo(t);
});
}
});

Now if we refresh page it’s keep the todos in todo list

See below video

See the complete code at the end of this step

Let’s build functionality for marking todo done and delete todo

Do you remember we created id property in todoobject and set data-key attribute to item in renderTodo function now we’re going to use these property and item to Mark todo complete and delete todo from todo list

Function for marking todo complete ( in toggleDone function we’re going to )

  1. Find the corresponding todoobject in the todoArray object
  2. Set checked property
  3. Call renderTodo function with todoArray index
js
function toggleDone(key) {
const index = todoArray.findIndex((myitem) => myitem.id === Number(key));
todoArray[index].checked = !todoArray[index].checked;
renderTodo(todoArray[index]);
console.log(todoArray[index]); // for demo purpose
console.log(index); // for demo purpose
}

In above code we created function toggleDone with key parameter , to mark done on specific todo from the todo list we have to know which todo we wanted to mark done for that we find the index of corresponding todoobject from the todoArray object using findIndex method and set its checked property to the opposite. That means, true will become false and vice versa then called renderTodo()

Call toggleDone function

js
const list = document.querySelector(".js-todo-list");
list.addEventListener("click", (event) => {
if (event.target.classList.contains("js-tick")) {
const itemKey = event.target.parentElement.dataset.key;
toggleDone(itemKey);
}
});

In above code we added click event listener to todo list if targeted event class list contains js-tick then get the dataset.key of their parent Element and assign to itemkey and call toggleDone() function and passed itemKey as an argument

Let’s run above code and see what’s happening

As you can see here we have problem when we mark todo done it will duplicate the todo items instead of marking todo done

To solve this we have to check if the current todo item exists in the DOM first then and replace it with the updated newlist item

Update renderTodo() function as shown below :

js
function renderTodo(todo) {
localStorage.setItem("todoArray", JSON.stringify(todoArray));
const list = document.querySelector(".todo-list");
const item = document.querySelector(`[data-key='${todo.id}']`);
const isChecked = todo.checked ? "done" : "";
const newlist = document.createElement("li");
newlist.setAttribute("class", `todo-item ${isChecked}`);
newlist.setAttribute("data-key", todo.id);
newlist.innerHTML = `
<input id="${todo.id}" type="checkbox"/>
<label for "${todo.id}" class="tick js-tick"></label>
<span>${todo.todoText}</span>
<button class="delete-todo js-delete-todo">
<button class="delete-todo js-delete-todo">
<svg fill="var(--svgcolor)" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
</button>
`;
// If the item already exists in the DOM
if (item) {
// replace it
list.replaceChild(newlist, item);
} else {
// otherwise append it to the end of the list
list.append(newlist);
}
}

Now let’s see how it’s works

As you can see above video it’s works as expected

See the complete code at the end of this step

Function for delete todo ( in deleteTodo function we’re going to )

  1. Find the corresponding todoobject from the todoArray object
  2. Create a new object with properties of the current todoobject item and a deleted property which is set to true
  3. Remove the todoobject item from the array
  4. Render emptytodo
js
function deleteTodo(key) {
const index = todoArray.findIndex((myitem) => myitem.id === Number(key));
const emptytodo = {
deleted: true,
...todoArray[index],
};
todoArray = todoArray.filter((myitem) => myitem.id !== Number(c));
renderTodo(emptytodo);
}

In above code we created function deleteTodo with key parameter , to delete specific todo from the todo list we have to know which todo we wanted to delete for that we find the index of corresponding todoobject from the todoArray object using findIndex method

Created a new object with properties of the current todoobject item by using spread operatror and array[index] and a deleted property which is set to true

Filter outed deleted todo object from todoArray object using filter method

When deleteTodo() called it will remove the todoobject item from the array otherwise it will not delete todo item by click on delete button from localstorage

Call deleteTodo function ( Similarly how we called toggleDone() )

js
const list = document.querySelector(".js-todo-list");
list.addEventListener("click", (event) => {
if (event.target.classList.contains("js-tick")) {
const itemKey = event.target.parentElement.dataset.key;
toggleDone(itemKey);
}
// delete todo
if (event.target.classList.contains("js-delete-todo")) {
const itemKey = event.target.parentElement.dataset.key;
deleteTodo(itemKey);
}
});

Finally let’s add remove method if emptytodo delete property true it will remove todo

Add below code in renderTodo function

js
if (todo.deleted) {
item.remove();
return;
}

Now as you can see below video we’re able to delete todo from todo list

Here we almost completed todo app project

Let’s add empty state when there is no todo to display ( optional )

Something like that

Let’s add empty state when there is no todo to display ( optional )

Markup of empty state ( add this markup below todo list markup in html )

html
<div class="empty-state">
<h1 class="app-title">My Todo</h1>
<i class="fas fa-tasks icon"></i>
<p class="empty-state__description">
Type in the below field 👇 and press enter to add your todo in the list
</p>
</div>

when there is no todo in todo list then add .empty-state and make display to flex ( add this below the other styles )

css
.todo-list:empty + .empty-state {
display: flex;
}

Styling for empty state .empty-state by default is none

( add this below the other styles )

css
.empty-state {
flex-direction: column;
align-items: center;
justify-content: center;
display: none;
}
.app-title {
text-align: center;
margin-bottom: 20px;
font-size: 80px;
}
.checklist-icon {
margin-bottom: 20px;
}
.icon {
font-size: 7rem;
}
.empty-state__title,
.empty-state__description {
font-size: 25px;
text-align: center;
margin: 2rem;
width: 25rem;
}

Congratulation we have successfully created todo app 🥳

Download source code