Add behavior to HTML using Alpine.js – a Todo app

Add behavior to HTML using Alpine.js – a Todo app

Sometimes I create pages with minimal interaction and amount of JavaScript – for instance, with a hamburger menu, toggle or tabs. They require simple yet repetitive JavaScript code. There are nice big components or frameworks that solve this problem (like Bootstrap, React, Vue), but the problem is their size and often a complexity to set up. Recently (in November 2019), a new library emerged – Alpine.js.


Alpine.js is a small library (8kB when transferred over the network) that can be used to declaratively add behavior to HTML pages, possible without using JavaScript code. It is quite fresh (4 months old at this moment) and it lacks various examples supporting the documentation, but the authors are supportive through the Issues page on GitHub. By the way, I found that library while checking out fresh Tailwind UI components, so maybe these projects are connected.

UPDATE: There are examples hosted on

Example – drop-down

The first examples on their page are very promising, e.g. creating a dropdown that appears after clicking a button and disappears when clicking outside the modal, requires just several attributes:

My related project

Timeline Creator

Create and export timeline graphs using an online tool


The first line with x-data inits an Alpine component. It will consist of one property: open, initialized to false.

The second line adds an event handler to a button. When it is clicked, it changes the component’s property open to true. @click is a shortcut from x-on:click.

The third line adds two behaviors. Firstly, it binds the visibility of ul to the value of the open property. So initially, when open=false, the ul element is hidden, and after clicking the button, the element appears.

Secondly, it adds a very neat functionality to set open=false and consequently, hide the ul element, when a user clicks away (outside) from that element.

If we don’t count those markups as JavaScript code, we have extremely useful and very common functionality built within seconds and without extra code.

Note that by adding one more word, we can add animation when the drop-down is appearing and appearing. Just replace x-show with x-show.transition.

Example – tabs

How about adding tabs to a page? Let me again explain an example from the documentation page:


The beginning is similar – we create a new Alpine component with the property tab equal to 'foo'. It will store the name of the currently visible tab.

Then there are two buttons with are the tab headers: Foo and Bar. Each has a click behavior to update the state and also an attribute to mark that tab button active. Namely, :class (shortcut from bind:class), listens to changes in the component’s state and adds or removes CSS classes from the element. In this case, a button will have the active CSS class if the currently selected tab is 'foo' or 'bar'. This means that exactly one button at any moment will have the active class and can be styled to mark the selection.

Finally, there are two divs with conditional visibility. Exactly one div will be shown, matching the button that was last clicked.

And again – it is possible to create a tab component with very clear and minimal inline markup.

Todo application

As a quick test of the library, I created a simple TODO application:


  • line 3: I decided to import the Alpine.js library from a CDN. Another option is installing the npm package and building it locally using a bundler / build tool. It is not explained here and not in the documentation yet, but I found some information in this article (apparently Todo applications are best to try a new framework…).
  • line 5: my component will have two properties: a list with tasks (items) and information whether the panel to add a new task is visible (adding).
  • line 6: a for loop is available in Alpine.js, too. The usage is simple: a template with for and key properties. The contents inside will be repeated for every element in the items array. I think only one child element should be used like here (a div); when I had span and button directly inside the template, Alpine rendered first all spans then all buttons. Funny.
  • line 8: it will display the item inside span. x-text sets the inner text, whereas x-html sets the inner HTML.
  • line 9: when clicking the DEL button, I remove that item from the items collection which results in removing the item from the screen.
  • line 13: when clicking the ADD button, the div below will be shown.
  • line 14: x-show.transition controls the visibility of the div, as explained in the first example. The transition parameter adds animation.
  • line 16: this is an input field to enter the new task’s name. There are two new things:
    • x-model="name" allows storing the value from this field in a special Alpine’s variable. In fact, it is a two-way binding which I’ll use soon.
    • @keyup (shortcut from x-on:keyup) adds a key up event handler. I use $refs.ok to refer to the HTML element which I named ok – see x-ref below. It is simpler than using document.getElementById() or another way to refer to the element. Next, I set the disabled property of that element to true if the length of the name variable (and therefore the contents of the input field) is zero. Thanks to this binding I disable the OK button if the field is empty and I enable it otherwise.
  • line 18: this button will close the adding task panel. I didn’t reset the name variable intentionally, so the value will remain in the field after reopening the panel.
  • line 19: clicking the OK button will do three things: hide the panel (adding=false), add the new task to the list (items.push(name) – the list will be automatically refreshed) and clear the field (name = ''). I also added a reference to this button under the name of ok with the x-ref attribute. I used it above.

Fixing the OK button

The previous example had one issue – the OK button was not correctly disabled when opening the panel. To fix it, I added two more things:

  • line 5: x-init allows executing code after the component is initialized. Here I disable the button when the page loads.
  • line 19: I added $refs.ok.disabled = true to disable the button after clicking OK. Apparently clearing the name variable was not sufficient.


Although I’m not a fan of using declarative annotations in HTML or inlining JavaScript, for some simple pages Alpine.js library seems to be a very useful solution. However, it has to be weighted with the cost of 32kB in extra library.

Full source code is available in a Gist.

4.1 22 votes
Article Rating

Want more?


Notify of
Inline Feedbacks
View all comments
1 year ago

I believe that Alpine is currently around 3KB, rather than the 32KB that that author suggests.

1 year ago

Hey! I testing alpine because i will lunch an javascript SPA project without big framework like vuejs. i really like vue but sometimes seams to be to oversized. Personally, i like VanillaJS. my current problem with alpine js is that the two way binding not work. i declare the variable count in my js. inline an div tag i put the alpine x-model inside and for displaying the count the x-text element. if i change the count manuel inside the console i cant binding the new value to the model count. is that an bug? maybe you have an idea? THX and pozdrowjenia do wawy

Would love your thoughts, please comment.x