Introduction
Hey, Developers,
Let's build a simple to-do app using only HTML, CSS, and JavaScript—no frameworks or libraries involved. This hands-on project will serve as an excellent starting point for beginners, providing insights into how JavaScript works, how it handles DOM manipulation, and how it manages user data.
We'll also learn how to add Icons and meta data to our website. So, when you share your website it will show related informations about our todo application.
let's dive in!
HTML Structure
create a index.html
file and copy paste this into your .html
file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- HTML Meta Tags -->
<title>Todo</title>
<meta name="description" content="Todo app built with HTML, CSS and, Javascript by mageshkannan">
<!-- Facebook Meta Tags -->
<meta property="og:url" content="https://magesh-sam.github.io/todo/">
<meta property="og:type" content="website">
<meta property="og:title" content="Todo">
<meta property="og:description" content="Todo app built with HTML, CSS and, Javascript by mageshkannan">
<meta property="og:image" content="https://cdn.pixabay.com/photo/2020/01/21/18/39/todo-4783676_1280.png">
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="https://cdn.pixabay.com/photo/2020/01/21/18/39/todo-4783676_1280.png">
<meta property="twitter:domain" content="magesh-sam.github.io">
<meta property="twitter:url" content="https://magesh-sam.github.io/todo/">
<meta name="twitter:title" content="Todo">
<meta name="twitter:description" content="Todo app built with HTML, CSS and, Javascript by mageshkannan">
<meta name="twitter:image" content="https://cdn.pixabay.com/photo/2020/01/21/18/39/todo-4783676_1280.png">
<!-- CSS Style sheet -->
<link rel="stylesheet" href="style.css" />
<!-- TWebsite Icon -->
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
<h1 align="center">Todo-App</h1>
<form >
<p class=" error error-hidden">Please enter the task name</p>
<div class="formfield">
<input required class="taskname" type="text" value="" name="taskname" id="Todo" placeholder="Buy groceries, walking,...">
<button type="submit" class="addbtn" >Add</button>
</div>
</form>
<section class="todolist" >
<div class="empty-todo">
<img src="https://raw.githubusercontent.com/Magesh-sam/todo/main/empty-todo.svg" alt="empty todo">
<p>Empty Todo</p>
</div>
</section>
<script src="app.js"></script>
</body>
</html>
With a form, we will capture the to-do input and store the data in localStorage with the help of JavaScript.
If you don't know about localStorage and how to handle forms to store user data, don't worry – we'll learn that soon!
<section>
tag is used to render the to-do list and the empty-todo section will be used If there is no todo is available to provide a better user experience.
CSS Styling
Don't worry, beginners! I've uploaded the project on GitHub, allowing us to seamlessly incorporate styles into our to-do app. Let's start by creating a style.css
file.
https://github.com/Magesh-sam/todo/blob/main/style.css
open this and copy paste in your .css
file.
I assume you already know the basics of css So I'll explain only few things. others are self explantionry
.error {
transition: all 300ms ease-in;
}
.error-visible {
display: block;
opacity: 100;
color: red;
font-size: 14px;
transition: all 300ms ease-in;
}
.error-hidden {
display: none;
opacity: 0;
transition: all 300ms ease-in;
}
This is used to show error message If the user submit the todo without typing anything.
@media (width<=620px) {
.empty-todo img {
display: none;
}
}
@media (width>620px) {
.empty-todo {
>p {
display: none;
}
}
}
To hide the image on smaller screens, we used media query. For responsive web design media query plays very crucial role.
I prefer Tailwind CSS due to its mobile-first approach and ease of developing responsive websites. Tailwind CSS can help you create responsive websites in no time.
@keyframes fadeup{
from{
opacity: 0;
transform: translateY(100%);
}
to{
opacity: 100%;
transform: translateY(0);
}
}
.todo{
animation: fadeup ease-in 300ms forwards;
}
This is a simple fade up animation which will occur when the user add new todo.
Don't forget to copy paste the style.css file from https://github.com/Magesh-sam/todo/blob/main/style.css
Feel free to play with CSS and create your own design!
This is how our application will look like now.
Javascript Magic
This is the most important part of any web applications.Since our UI is done Now, let's add some life to our app using JavaScript. We'll handle user input, update the DOM dynamically, and manage the to-do list.
Let's get the element from the DOM
const todoInput = document.querySelector('.taskname');
const addBtn = document.querySelector('.addbtn');
const error = document.querySelector('.error')
const todoList = document.querySelector('.todolist')
const emptyTodo = document.querySelector('.empty-todo')
const deleteBtns = document.querySelectorAll('.deletebtn');
The querySelector is used to get the elements from the DOM. in this we hav mentioned class names to fetch elements.
Let's get the form data
to store to-do task we need a variable and array to store list of todos. so, let's create one .
let taskName = "";
let todos = [];
let's add event listener to out input element so we can get the user input.
todoInput.addEventListener('change', (e) => {
taskName = e.target.value;
})
Now we got our user's input. let's add it to array but before that how can we add?
ummm...... we already added add button so that we can submit the form data. but the button won't work just like that. let's add event listener so that we can get the user input, generate a todo and submit to the todos array. we'll talk about addTodo()
later.
addBtn.addEventListener('click', (e) => {
e.preventDefault();
if (taskName.length === 0) {
error.classList.add('error-visible')
error.classList.remove('error-hidden')
}
else {
addTodo(taskName)
}
})
We already talked about user experience in css section. Now with the help of js show the error. if the user submit an empty form. we will show the user the form cannot be submitted empty. so, they can add task name.
todoInput.addEventListener('change', (e) => {
taskName = e.target.value;
error.classList.remove('error-visible')
error.classList.add('error-hidden')
})
todoInput.addEventListener('focus', () => {
error.classList.remove('error-visible')
error.classList.add('error-hidden')
})
addBtn.addEventListener('click', (e) => {
e.preventDefault();
if (taskName.length === 0) {
error.classList.add('error-visible')
error.classList.remove('error-hidden')
}
else {
addTodo(taskName)
}
})
Now we've added error message for best user experience. So if the user starts typing we've to remove the error message right? yeah for the we've added focus, click
event listener. So if a user foucs the input or start typing the error message will disappear.
Let's talk about addTodo()
function
const addTodo = (taskName) => {
const newTask = {
taskName,
isCompleted: false,
id: crypto.randomUUID()
}
todos.push(newTask);
todoInput.value = ""
createTodoElemenet(newTask)
localStorage.setItem("todos", JSON.stringify(todos))
}
now we received and submitted the user input let's create an object with taskname, status and Id to manage the todo.
crypto.randomUUID() is used to generate random id.
after creating the todo we are pushing the newTask to todos array. so that we can print the data in the UI.
after the pushing the value we are resetting the input value to create a new todo.
in the next section we'll talk about createTodoElemenet(newTask)
function and how to render the UI.
but before that the last line which is used to store that data in localstorage.
local storage API used to persist the data in browser storage for later use. Untill we delete the data it'll be present in the browser storage.
in the localstorage we can only store string. so before storing our data we are converting out todos array into string with the help of JSON.stringfy()
method.
localStorage.setitem(key,value)
This is how we store our data in the browser storage.
Let's talk about rendering UI
Whenever we add a todo we call addTodo()
function to create todo and it'll call createTodoElement()
functin to create a todo DOM element.
const createTodoElemenet = (newTask) => {
const todoElement = document.createElement('div');
todoElement.classList.add("todo");
todoElement.id = newTask.id;
const todoCheckbox = document.createElement('input');
todoCheckbox.type = 'checkbox';
todoCheckbox.id=newTask.id;
todoCheckbox.checked = newTask.isCompleted
todoCheckbox.classList.add("todocheckbox")
todoCheckbox.checked = newTask.isCompleted;
const textElement = document.createElement('p')
textElement.textContent = newTask.taskName;
const deleteBtn = document.createElement('button');
deleteBtn.textContent = "❌"
deleteBtn.classList.add("deletebtn")
deleteBtn.id=newTask.id;
todoElement.append(todoCheckbox, textElement, deleteBtn)
todoList.append(todoElement)
}
This function create elements with help of document.createElement()
function.
it first create a wrapper div
Element followed by a checkbox
to handle todo completion status, paragraph
to render the taskname and finally a button
to delete the todo.
Finally we append the checkbox
, p
and button
to the wrapper div
element.
After wrapping the the child elements into the wrapper div
we are appending the todo Element to the main todoList section.
🎉 Now we can render todos. but wait... yeah! we have not implemented deleteTodo and toggleCheckbox yet. Let's start that immediately.
DeleteTodo and ToggleTodoStatus
const deleteTodo = (id) =>{
const consent = confirm("Are you sure want to remove this?")
if(!consent){
return
}
const todoIndex = todos.findIndex(todo => todo.id === id);
const todoElement = document.getElementById(id)
if(todoElement){
todoElement.remove()
}
if(todoIndex !== 1){
todos.splice(todoIndex,1)
}
localStorage.setItem("todos", JSON.stringify(todos));
toggleEmptyTodo();
}
const toggleTodoStatus = (id) => {
const todo = todos.find(todo => todo.id === id);
const todoElement = document.getElementById(id)
if(todo){
todo.isCompleted = !todo?.isCompleted
}
if(todoElement){
todoElement.style.background = todo.isCompleted ? "#bbb8b840" : ""
}
localStorage.setItem("todos", JSON.stringify(todos));
}
todoList.addEventListener('click',(e)=>{
if(e.target.classList.contains('deletebtn')){
const id = e.target.id;
deleteTodo(id);
}
if(e.target.classList.contains('todocheckbox')){
const id = e.target.id;
toggleTodoStatus(id)
}
})
DeleteTodo()
by adding event listener to the parent todolist container we can avoid rewriting same thing again and again. so we have added click event listener to the todolist with the event parameter we can find which element is being clicked not only that we can access the parent element's data too.
sso first with the classList we decide which element is being clicked. then we are taking the parent's id to perform delete operation.
First with the id value we find the element and their index to remove from the dom. If the element is present we are removing from the dom and todos array and store the array back to localStorage so they'll in sync with the UI.
toggleEmptyTodo() method checkes if the todo list is empty . if the list is it renders the fallback UI to provide a better user experience.
ToggleTodoStatus.
just like delete todo we do the same thing till getting the id and find the todo and element. after finding the todo we will set the todo status with the help of !
operator. so what is the use of !. in js this is a NOT operator so if a value is true it will return false and vice versa. So basically we are ressigning the opposite value of the todo status to itself. and we are syncing the data with localStorage. Based on todo status we add some styling to the todo element. so the user can differenciate between completed and pending tasks.
Summary.
This is a simple todo app build with HTML, CSS and Javascript. in this we learnt about DOM manipulation with javascript. Handling form data. Managing user data in local storage. Rendering UI with javascript. Applying styles with javscript based on conditions. How to create a HTML element and append them to parent.
The resources Needed to develop this project