Lexington has been awarded a grant from Astro, to celebrate. Get a 30% discount. Apply code LEXINGTON30 ( uppercase ) at checkout.

← Back to all tutorials

How to create a TODO with Tailwind CSS and JavaScript

#_
Published and written on May 20 2024 by Michael Andreuzza

It’s Monday! Let’s recreate the TODO app from the previous tutorial using Alpine JS but with JavaScript instead.

A refresh of what a todo app is

A TODO is a list of things that need to be done. It’s a great way to keep track of tasks and prioritize them. It can be used for anything from personal projects to work tasks.

Structure

Understanding the code:

  • id="todo-component": This is the ID of the component that contains the input to add new todo and the list of todos.
  • id="addTodoButton": This is the ID of the button that adds a new todo when clicked.
  • id="newTodoInput": This is the ID of the input that holds the new todo.
  • id="todoList": This is the ID of the list that displays the todos.

Classes are removed for brevity, but I’ll keep those classes relevant to the tutorial.

<div id="todo-component">
  <!-- Input to add new todo -->
  <div >
    <button
      id="addTodoButton"
      >Add</button
    >
    <input
      id="newTodoInput"
    />
  </div>
  <!-- List of todos -->
  <ul
    id="todoList">
  </ul>
</div>

The script

  • document.addEventListener("DOMContentLoaded", () => {: This is the event listener that will be used to add the event listeners to the DOM elements.
  • const todos = JSON.parse(localStorage.getItem("todos")) || [];: This is the variable that will hold the todos.
  • const todoList = document.getElementById("todoList");: This is the variable that will hold the list of todos.
  • const newTodoInput = document.getElementById("newTodoInput");: This is the variable that will hold the input to add new todo.
  • const addTodoButton = document.getElementById("addTodoButton");: This is the variable that will hold the button that adds a new todo.
  • const renderTodos = () => {: This is the function that will be used to render the todos.
  • todoList.innerHTML = todos: This is the code that will be used to render the todos.
  • document: This is the variable that will be used to access the DOM elements.
  • querySelectorAll('input[type="checkbox"]'): This is the code that will be used to access the checkboxes in the list of todos.
  • forEach((checkbox, i) => {: This is the code that will be used to iterate over the checkboxes in the list of todos.
  • checkbox.addEventListener("change", () => {: This is the code that will be used to add an event listener to the checkboxes in the list of todos.
  • todos[i].completed = checkbox.checked;: This is the code that will be used to update the completed property of the todo based on the checked state of the checkbox.
  • localStorage.setItem("todos", JSON.stringify(todos));: This is the code that will be used to update the local storage with the new completed state of the todo.
  • renderTodos();: This is the code that will be used to render the todos again.
  • const addTodo = () => {: This is the function that will be used to add a new todo.
  • const newTodoText = newTodoInput.value.trim();: This is the code that will be used to get the value of the new todo input.
  • if (newTodoText) {: This is the code that will be used to check if the new todo input is not empty.
  • todos.push({: This is the code that will be used to add a new todo to the todos array.
  • text: newTodoText,: This is the code that will be used to set the text property of the new todo.
  • completed: false: This is the code that will be used to set the completed property of the new todo to false.
  • localStorage.setItem("todos", JSON.stringify(todos));: This is the code that will be used to update the local storage with the new todo.
  • renderTodos();: This is the code that will be used to render the todos again.
  • addTodoButton.addEventListener("click", addTodo);: This is the code that will be used to add an event listener to the addTodoButton.
  • newTodoInput.addEventListener("keydown", (e) => e.key === "Enter" && addTodo());: This is the code that will be used to add an event listener to the newTodoInput.
  • renderTodos();: This is the code that will be used to render the todos again.

The full script

document.addEventListener("DOMContentLoaded", () => {
    const todos = JSON.parse(localStorage.getItem("todos")) || [];
    const todoList = document.getElementById("todoList");
    const newTodoInput = document.getElementById("newTodoInput");
    const addTodoButton = document.getElementById("addTodoButton");

    const renderTodos = () => {
        todoList.innerHTML = todos
            .map(
                (todo, i) => `
        <li class="flex items-center space-x-2 w-full py-2">
          <div class="flex items-center justify-between w-full">
            <div>
              <input type="checkbox" ${todo.completed ? "checked" : ""} class="form-checkbox h-4 w-4 rounded border-neutral-300 text-orange-600 focus:ring-orange-600"/>
              <span class="text-lg text-neutral-500 ${todo.completed ? "line-through" : ""}">${todo.text}</span>
            </div>
            <button class="text-black hover:text-red-700 focus:outline-none ml-auto">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
                <path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z"></path>
              </svg>
            </button>
          </div>
        </li>
      `
            )
            .join("");

        document
            .querySelectorAll('input[type="checkbox"]')
            .forEach((checkbox, i) => {
                checkbox.addEventListener("change", () => {
                    todos[i].completed = checkbox.checked;
                    localStorage.setItem("todos", JSON.stringify(todos));
                    renderTodos();
                });
            });

        document.querySelectorAll("button.text-black").forEach((button, i) => {
            button.addEventListener("click", () => {
                todos.splice(i, 1);
                localStorage.setItem("todos", JSON.stringify(todos));
                renderTodos();
            });
        });
    };

    const addTodo = () => {
        const newTodoText = newTodoInput.value.trim();
        if (newTodoText) {
            todos.push({
                text: newTodoText,
                completed: false
            });
            newTodoInput.value = "";
            localStorage.setItem("todos", JSON.stringify(todos));
            renderTodos();
        }
    };

    addTodoButton.addEventListener("click", addTodo);
    newTodoInput.addEventListener(
        "keydown",
        (e) => e.key === "Enter" && addTodo()
    );

    renderTodos();
});

Conclusion

This is a simple TODO app that can be used to keep track of tasks and prioritize them. It can be used for anything from personal projects to work tasks. Remember that before using this code you will have to make it accessible to your users by adding the necessary HTML and CSS or Tailwind CSS or whatever you are using.

Hope you enjoyed this tutorial and have a great day!

/Michael Andreuzza

Did you like this tutorial? Please share it with your friends!

Get lifetime access to every theme available today for $199 and own them forever. Plus, new themes, lifetime updates, use on unlimited projects and enjoy lifetime support.

— No subscription required!