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.
Table of contents
Introduction
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 http://www.alpinetoolbox.com/.
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:
Note: I'll soon be sharing short, practical tips on Angular — a good way to pick up something new if you're interested.
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:

Code:
- 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
withfor
andkey
properties. The contents inside will be repeated for every element in theitems
array. I think only one child element should be used like here (adiv
); when I hadspan
andbutton
directly inside thetemplate
, Alpine rendered first allspan
s then allbutton
s. Funny. - line 8: it will display the item inside
span
.x-text
sets the inner text, whereasx-html
sets the inner HTML. - line 9: when clicking the DEL button, I remove that
item
from theitems
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. Thetransition
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 fromx-on:keyup
) adds a key up event handler. I use$refs.ok
to refer to the HTML element which I namedok
– seex-ref
below. It is simpler than using document.getElementById() or another way to refer to the element. Next, I set thedisabled
property of that element totrue
if the length of thename
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 ofok
with thex-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 thename
variable was not sufficient.
Summary
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.
I believe that Alpine is currently around 3KB, rather than the 32KB that that author suggests.
Thanks for correcting. I have just checked that the minified gzipped file is 8.5 kB (uncompressed 25 kB).
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