How to create a TodoApp using AlpineJS?

How to create a TodoApp using AlpineJS?

In modern web development, interactivity is a must. Developers often seek lightweight solutions to make their web applications dynamic without the overhead of heavy frameworks. AlpineJS, a minimalist JavaScript framework designed to provide reactive and declarative capabilities directly in your HTML. Its simplicity and capability make it a favorite for developers who want to enhance their UI without diving into complex configurations.

In this article, we’ll demonstrate how to create a simple TodoApp using AlpineJS, HTML, and TailwindCSS. You can also use other CSS libraries like bootstrap as you wish.

Setting Up the Project

This project is written in a single HTML file.
First, include the required dependencies for AlpineJS and TailwindCSS in your HTML file:

<head>
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
  <script src="https://cdn.tailwindcss.com"></script>
</head>

These links load AlpineJS and TailwindCSS via their respective CDNs, making setup straightforward. However if you do not want to use CDN for Alpine JS, then you can refer this article to use AlpineJS by installing through npm (node package manager).

Structuring the HTML

As in the above screenshot, this TodoApp will have basic fields:

  • An input field to accept the ‘task’ entered in the  input field.
  • A list of of todo items
  • A button to add a todo in the list
  • Additionally there is a delete button for deleting each item.

Here is the skeleton of the todo app without AlpineJS and CSS, i.e. only html

<body>
    <div>
        <div>
            <h1>
                AlpineJS Todo List
            </h1>

            <!-- Input & Add Todo -->
            <div>
                <textarea
                    type="text"
                    id="todo-input"
                    placeholder="Add a new task..."
                >
                </textarea>
                <button>
                    Add Todo
                </button>
            </div>

            <ul id="todo-list">
                <!-- This section will be rendered by AlpineJS-->
                <!-- <li> <span> Todo item </span> </li>-->
                <!-- <li> <span> Todo item </span> </li>-->
            </ul>
        </div>
    </div>
</body>

After adding Tailwind CSS it will look like this:

<body class="bg-gray-100 font-sans antialiased">
    <div>
      <div class="max-w-lg mx-auto mt-10 p-6 bg-white shadow-md rounded-lg">
        <h1 class="text-3xl font-semibold text-center text-gray-700">
          AlpineJS Todo List
        </h1>

        <!-- Input & Add Todo -->
        <div class="mt-6">
          <textarea
            type="text"
            id="todo-input"
            placeholder="Add a new task..."
            class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 mb-4"
          >
          </textarea>
          <button
            class="w-full py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none"
          >
            Add Todo
          </button>
        </div>

        <ul id="todo-list" class="mt-4 space-y-4">
          <!-- This section will be rendered by AlpineJS-->
          <!-- <li> <span> Todo item </span> </li>-->
          <!-- <li> <span> Todo item </span> </li>-->
        </ul>
      </div>
    </div>
  </body>

Again after adding alpinejs it will be like this:

<body class="bg-gray-100 font-sans antialiased">
    <div
      x-data="{
      task: '',
      taskList: [],
      addTaskToTaskList(){
        if(this.task){
          this.taskList.push(this.task);
        }
      }
    }"
    >
      <div class="max-w-lg mx-auto mt-10 p-6 bg-white shadow-md rounded-lg">
        <h1 class="text-3xl font-semibold text-center text-gray-700">
          AlpineJS Todo List
        </h1>

        <!-- Input & Add Todo -->
        <div class="mt-6">
          <textarea
            type="text"
            id="todo-input"
            placeholder="Add a new task..."
            class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 mb-4"
            x-model="task"
            x-on:keydown="if ($event.key === 'Enter' && !$event.shiftKey) { $event.preventDefault() }"
            x-on:keyup.enter="addTaskToTaskList"
          >
          </textarea>
          <button
            x-on:click="addTaskToTaskList"
            class="w-full py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none"
          >
            Add Todo
          </button>
        </div>

        <ul id="todo-list" class="mt-4 space-y-4">
          <template x-for="(item,index) in taskList" :key="index">
            <li
              class="flex items-center justify-between p-2 bg-gray-50 rounded-md shadow-sm"
            >
              <span x-text="item"></span>
              <button
                x-on:click="taskList.splice(index, 1);"
                class="ml-2 text-red-500 'hover:text-red-600"
              >
                🗑️
              </button>
            </li>
          </template>
        </ul>
      </div>
    </div>
  </body>

Explanation:

AlpineJS Parts 

  1. Declaration Part:
x-data="{
      task: '',
      taskList: [],
      addTaskToTaskList(){
        if(this.task){
          this.taskList.push(this.task);
        }
      }
    }"

In x-data variables and functions are declared. Here, task/todo will store state of each todo item at the time of input and taskList will contains all the list of todo items. addTaskToTaskList() function is a normal javascript function and it will be used to ass the task/todo in the taskList.

2. Model and Input Event Handling:

x-model="task"
x-on:keydown="if ($event.key === 'Enter' && !$event.shiftKey) { $event.preventDefault() }"
x-on:keyup.enter="addTaskToTaskList"

3. Todo Item / Task Listing:

<template x-for="(item,index) in taskList" :key="index">
   <li class="flex items-center justify-between p-2 bg-gray-50 rounded-md shadow-sm">
      <span x-text="item"></span>
      <button
         x-on:click="taskList.splice(index, 1);"
         class="ml-2 text-red-500 'hover:text-red-600"
       > 🗑️</button>
    </li>
</template>

Reactive Data Binding:

  1. x-data: Defines the AlpineJS component and its reactive state (task and taskList).
  2. x-model: Binds the input value to the task property in the state.

Event Handling:

  1. x-on:click: Adds the current task to the taskList when the “Add Todo” button is clicked.
  2. if ($event.key === ‘Enter’ && !$event.shiftKey) { $event.preventDefault() }

This code is used to prevent the textarea to create a newline on pressing of normal “Enter” key. However it will create newline only when Shift + Enter keys are pressed

  1. x-on:keyup.enter: Listens for the “Enter” key to add tasks without clicking the button.

Dynamic List Rendering:

  1. x-for: Iterates over the taskList array to display tasks.
  2. x-on:click inside the list: Removes a task from the array when the delete button is clicked.

Final Thoughts

With just a few lines of code, AlpineJS enables the creation of a fully functional, responsive TodoApp. Its integration with TailwindCSS further enhances the development experience, providing a visually appealing UI with minimal effort.

Whether you’re building a small widget or enhancing a larger project, AlpineJS is a fantastic choice for developers seeking simplicity and power. Give it a try and unlock a new way of creating dynamic web applications’

The complete code is given below:

<html>
  <head>
    <script
      defer
      src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
    ></script>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body class="bg-gray-100 font-sans antialiased">
    <div
      x-data="{
      task: '',
      taskList: [],
      addTaskToTaskList(){
        if(this.task){
          this.taskList.push(this.task);
        }
      }
    }"
    >
      <div class="max-w-lg mx-auto mt-10 p-6 bg-white shadow-md rounded-lg">
        <h1 class="text-3xl font-semibold text-center text-gray-700">
          AlpineJS Todo List
        </h1>

        <!-- Input & Add Todo -->
        <div class="mt-6">
          <textarea
            type="text"
            id="todo-input"
            placeholder="Add a new task..."
            class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 mb-4"
            x-model="task"
            x-on:keydown="if ($event.key === 'Enter' && !$event.shiftKey) { $event.preventDefault() }"
            x-on:keyup.enter="addTaskToTaskList"
          >
          </textarea>
          <button
            x-on:click="addTaskToTaskList"
            class="w-full py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none"
          >
            Add Todo
          </button>
        </div>

        <ul id="todo-list" class="mt-4 space-y-4">
          <template x-for="(item,index) in taskList" :key="index">
            <li
              class="flex items-center justify-between p-2 bg-gray-50 rounded-md shadow-sm"
            >
              <span x-text="item"></span>
              <button
                x-on:click="taskList.splice(index, 1);"
                class="ml-2 text-red-500 'hover:text-red-600"
              >
                🗑️
              </button>
            </li>
          </template>
        </ul>
      </div>
    </div>
  </body>
</html>

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *