To Do List Application with Alpine JS
We will see the development to implement a To Do List application and its CRUD with Alpine JS.
We will see the steps to create a To Do List type app as a first real and functional project.
Create
The next functionality that we are going to create would be to create a whole for this we are going to expand a simple form like the one you are seeing on the screen here with an input exactly like the one we have here:
<form x-on:submit.prevent="save()" class="row g-2 mt-2">
<div class="col-auto">
<label class="col-form-label">Create</label>
</div>
<div class="col-auto">
<input type="text" x-model="task" class="form-control">
</div>
<div class="col-auto">
<button class="btn btn-success" type="submit">Save</button>
</div>
</form>
What is important but with another name, that is to say another variable, for the rest here we have the submit type button, therefore here it could be said that for this to make sense we have to place a form here but to avoid the form being sent via an html letter, that is to say that when we click on it the entire page reloads, which we do not want, then here we prevent the sending of the same so that it does not reload the entire page again and it would be assumed there that we were sending a request to the server again, which we do not want since we all want to keep it here locally and for that here we place the following submit prevent attribute:
submit.prevent
What is to handle the events then we do is a Push to the array of all and obviously we put complete here in false:
todos: [],
***
save() {
this.todos.push(
{ completed: false, task: this.task }
)
}
Because it is being created it is supposed to be not completed and if you want to complete it you should check it afterwards and we will clean the variable here then it is simply that we are going to come back here and we are going to create it, I am going to place it here we place form I said form again the action or nothing this interests us because it is a local form and to avoid it being sent we place xon submit point primen and we call the function that we are going to implement the call save and here we can place an input a Button of type submit here we have it I am going to place it as save and here we place the label I am going to place create and here we place the input of type text good This is not type text Here it is we remove all this and we are going to call it the x-model or we are going to place the attribute of X model with the attribute of Tas well already here we finish what is missing is to create the variable here we call it task and we create here the save function that is why you can see that we create all this a separate component and not directly in the html since we have multiple functions and above it would become a mess we place ts again point all the array in this case Now this is something through javascript to add an element to an array which is what we have here we can expand since there are many ways the Push function which you receive the element already written is quite open you can pass a number directly if you want but in this case we want to respect the structure that we already have and so you can see that it is exactly the same I am going to copy it and I am going to place it here again because it is the same structure that we want to maintain so that the application makes sense what I would change here would be the value that in this case you would place and here I am going to place it false because again I do not want it to be completed as soon as I add and this would be practically everything.
Search Field
The next functionality that we are going to implement or the first functionality would be a filter or search field for this what we are going to do is use a text type input we place the x-model we already know what the user writes we can also have it saved in a variable that we are going to create called search and around here this variable notice that here too now we are iterating through a function that we are also going to implement and through the aforementioned variable we look for matches with what we already have So initially it has nothing as you can see therefore it would return all the records:
<script>
function data() {
return {
search: "",
todos: [{ completed: true, task: 'TO DO 1' }, { completed: false, task: 'TO DO 2' }, { completed: false, task: 'TO DO 3' }],
completed(todo) {
return todo.completed
},
incompleted(todo) {
return !todo.completed
},
totalTareas() {
return this.todos.length
},
filterTodo() {
return this.todos.filter((t) => t.task.toLowerCase().includes(this.search.toLowerCase())
)
},
}
}
</script>
But as soon as the user starts typing, it will be filtered and how the filter works in javascript we have a function called filter that in the end also works similar to for each and so on, that is, we implement it through a callback or a method without a name, a function better said, in which here we have the item, that is, the element that we are iterating over and we apply the condition if this condition:
<div x-data="data()">
<p>Total Task <span x-text="totalTareas"></span></p>
<label>Search
<input type="text" x-model="search">
</label>
<template x-for="t in filterTodo()">
<p>
<template x-if="completed(t)">
<span>Completed</span>
</template>
<template x-if="!t.completed">
<span>Uncompleted</span>
</template>
<span x-text="t.task"></span>
</p>
</template>
</div>
Returning true means that it is part of the list that we are returning here and if not it simply excludes it, therefore by default here we already have a filtered list again if this is empty which would be the initial condition it would return all the elements here we also use the toLowerCase function which is to convert the text to lowercase which is what we are comparing here:
return this.todos.filter((t) => t.task.toLowerCase().includes(this.search.toLowerCase())
and over here in case the user for example here says take out the trash then he writes take out with the lowercase s then it is as if to say to integrate everything and regardless of whether it is uppercase or lowercase it also returns the same result Because if one had to do an exact match, which again we would not want so we are going to start here I am going to place the Label And now we create the variable and here the input we place input of type text we remove all this we place x model and we are going to call it as share over here we are going to register it we place shar it is going to be equal to empty and we create the new filter function everything here we apply the condition as we told you this is going to return is a raate is provided by t dot all Remember that to reference any of these variable methods we have to place the tis as it happens with Vue as if it were a class then here we reference es is that of all as we did also here only in this case we want is to apply the javascript filter function very important that works in the following way in case you do not know it also the documentation of moxila also has some very interesting examples with this in case you still don't understand but this is basically a callback in which this has to return a condition of true or false you can also go for the classic if that's what you want but since the condition is in a single line we can take advantage of the arrow functions that we have in javascript that has the following syntax well here we are going to place the condition remember here we have the whole that we are iterating over we want is to access the task the text we convert it to lowercase with the to lowercase function and finally here we compare if this is included in the search term that the user just placed and this returns true or false then we place t. share to access the variable that we just created point toLowerCase again to convert everything to lowercase and pending which is a function and this would return the condition of true or false you can also run it in case you don't see it here in your javascript console this token there well this function is the one we are going to use now here to iterate and this would be practically everything so here we reload here we have it Remember what this is good This is shocking me a little bit here later we are going to place a little icon but it is also going to place a dash to make a separation here and to be able to visualize this in a better way these are all the ones that I have I am going to place for example one Notice that it finds it perfectly because it is the only one that has one included everything was written in lowercase notice that it takes it by the condition that we placed here if you do not place this then it would have to do an exact match which is usually not what we want notice that it wrote everything and it does not find it if I put it in uppercase it finds it so that easy we already implemented a filter a simple filter for our todo list app.
To Do completed
The next operation we are going to do is mark a whole as completed. For this we will use the checkbox type input and we will set it. Note that it is not a variable that we are defining but rather, as they say, it is live. Here, from the whole, we set a field called complete, which by default would not have been completed, as we have done up to this point:
***
<template x-for="t in filterTodo()">
<input type="checkbox" x-model="t.completed">
<span x-text="t.task"></span>
</template>
***
Remember that this complete field would be this one then we simply bind I mean live because it is not set in this way but is part of an array and this is dynamic maybe for example this one may not be there maybe the user creates another etcetera then clarified is a little bit let's do it here quickly let's place it right in the list remember that this is for now later we choose something prettier and I'm going to place it here we place an input again remember that here is the iteration of type checkbox and we place x model equals point complete don't expect it to do much because again all this Data we are handling it here locally later we configure it to the iw the server if you are following the iware course but for now we are satisfied with at least having the functionality here and notice that the message changes automatically here too by the way I placed it was a complete that would be the correct name so let's go to the next class.
To Do Eliminar
The next operation that we are going to perform is the possibility of deleting a whole would be the one we have here very simple again remember that everything is kept local when we reload it will be lost or the scheme that we currently have will be maintained in my case it is this but to have it here ready then for this what do we do a button of course it is again without design at least for the moment and around here there are 1000 ways to delete an element from an array in javascript important that this is javascript and one of them is the filter since we are expanding the filter to filter it here we can also take advantage of it what does the condition do well basically iterate all the wholes and the one that is different from the whole that we want to eliminate which is the one we are passing here is kept in essence it will keep all the wholes except the one we are passing which is the one we want to eliminate in the same way any doubts Do the test in the browser console and there you will see how it works but that is what we are going to implement here the method well the function we call it remove here we receive the whole that we want to eliminate all:
remove(todo) {this.todos = this.todos.filter((t) => t != todo)},
Remember that all of this is reactive therefore it is not necessary to do as they say any additional operation to reload the list that is the beauty of this t dot all will be equal to dis dot all dot all dot filter as I was telling you one of the ways we have to eliminate a specific element from the list and what we do is T is different from all remember in my case for example I have three all we iterate those three and all the ones that are different that would be all would be the other two all are kept but the all that is equal to the one we want to eliminate that would be one of the three all that I have there is the one that we would remove from here therefore this in my example that I have again three all would return the two all that we do not want to eliminate and would eliminate one I hope it is clear Likewise any query to the comments box Here I have it and I did not implement the button So we implement it I am going to place it here too we place:
***
<template x-for="t in filterTodo()">
<input type="checkbox" x-model="t.completed">
<span x-text="t.task"></span>
<button @click="remove(t)">X</button>
</template>
***
Again when you reload, obviously what we have initially is maintained because we have it in hardcode and if we create one here it is and we can delete it without problems. I'll go again and place something else here and notice that everything works correctly, so with this we implement said functionality.
- Andrés Cruz
This material is part of my complete course and book; You can purchase them from the books and/or courses section, Curso y Libro primeros pasos con Laravel 11 Livewire 3 + Alpine.js y Tailwind.css - 2025.
Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter