Back in the old days, when I was building web sites by hand, in the snow, uphill, both ways, jQuery was my default tool when building any kind of interactivity on a web page. Way before I even considered building apps, jQuery was the workhorse that made cross-browser web development easy, or at least a little less painful. In 2024, it’s still in use on the vast majority of web sites. (I’ve seen various numbers, but all point to at least roughly three fourths of the web sites in use today.)
I think part of the reason jQuery was so successful is that, along with patching browser incompatibilities (looking at you, Safari & Internet Explorer), it was a laser focused set of utilities for common things developers needed to do. Mainly:
- Making network requests (without the pain of
XMLHttpRequest
) - Listening for events in the DOM
- Making changes in the DOM
It did a lot more than that, but those three items are part of every interactive web page I’ve built since the introduction of JavaScript. This is why I’ve been so enamored of late with Alpine.js. Alpine.js is lightweight (44kb minified, around half of jQuery’s size) and simple enough that the entire thing (at a high level), is documented on the home page. Let’s quickly go over the basics, and hopefully you’ll fall in love as well!
Installation and Setup
You can do a npm install alpinejs
if you want, but it’s easier to drop the CDN link in the head of your web page:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
Code language: HTML, XML (xml)
The next part requires a bit of thought. jQuery, once installed, was available everywhere and you could do just about anything. If you ever used Vue.js for progressive enhancement (versus as your entire application), you might remember that you had to specify the part of the DOM that Vue would work with. Basically, “This area of the DOM will make use of variables, have event handlers I track and so forth.” This usually was the main block of your web page, not the header and footer.
Alpine is the same way. To keep things simple, I’ll be using one main <div>
block for this purpose. This is done with an x-data
attribute:
<div x-data="stuff here in a bit...">
</div>
Code language: HTML, XML (xml)
OK, ready to get started?
Setting and Displaying Variables
Let’s begin by initializing a few variables and showing how Alpine renders them. First, to define variables, you can set them as an object inside x-data
:
<div x-data="{
name:'Raymond',
age:51,
cool:false
}">
</div>
Code language: HTML, XML (xml)
To display these values, you can use two different directives. The x-text
attribute will bind the text value of a variable to the DOM while x-html
will bind HTML. Which you use depends on the data. Here’s this in action:
<div x-data="{
name:'Raymond',
age:51,
cool:false
}">
<p>
My name is <span x-text="name"></span>.
I'm <span x-text="age"></span> years old.
</p>
</div>
Code language: HTML, XML (xml)
Unlike Vue, Alpine.js doesn’t have a “mustache-like” language built in, but instead relies on attributes applied to your DOM itself. In this case, span
tags. You could bind them to any element, but span
s make sense here for simple values. I will admit, it is a bit verbose, but it’s nice that it’s easy to spot in code. You can see this in action below (and feel free to edit the values of course):
That’s basic “render data from JavaScript in the DOM”, but let’s now show how to conditionally show information. Like Vue, Alpine provides two methods. The first, x-show
, will hide or display items in the DOM based on their value:
<div x-data="{
name:'Raymond',
age:51,
cool:false
}">
<p>
My name is <span x-text="name"></span>.
I'm <span x-text="age"></span> years old.
</p>
<p x-show="cool">
Oh, and I'm so darn cool!
</p>
</div>
Code language: HTML, XML (xml)
The other method, x-if
, will add and remove the contents of your DOM based on the value. Because of this, it requires you make use of template
and use one top level root element, like a div
or p
. I’ll admit this tripped me up from time to time, but Alpine does a good job of reporting the issue if you screw this up. Here’s the same example as above, re-written with x-if
:
<template x-if="cool">
<p>
Did I mention I'm cool?
</p>
</template>
Code language: HTML, XML (xml)
You can test this below. Switch the value of cool
to see it in action:
Finally, what about looping? Alpine provides the x-for
directive. Like x-if
, you’ll use a template
tag with one root element. Here’s an example:
<div x-data="{
name:'Raymond',
age:51,
cool:false,
hobbies:['building cat demos','star wars','cats']
}">
<p>
My name is <span x-text="name"></span>.
I'm <span x-text="age"></span> years old.
</p>
<ul>
<template x-for="hobby in hobbies">
<li x-text="hobby"></li>
</template>
</ul>
</div>
Code language: HTML, XML (xml)
Note the use of “variable in array” syntax. You can use whatever you want here, but name it something sensible. Also, in the example above I’m looping over an array of strings. You can loop over an array of objects as well. Check it out in the embed below:
Two-Way Binding
How about binding form fields to your data? Once again, Alpine provides a directive, x-model
. This works on any form field except file inputs (although, like in Vue, there’s a workaround).
For example:
<label for="firstName">First Name:</label>
<input id="firstName" x-model="firstName">
<br />
<label for="lastName">Last Name:</label>
<input id="lastName" x-model="lastName"><br/>
<label for="cool">Cool?</label>
<select id="cool" x-model="cool">
<option>true</option>
<option>false</option>
</select>
Code language: HTML, XML (xml)
The embed below demonstrates this, along with conditionally showing content based on the dropdown:
Binding Attributes
Closely related to two-way binding of form fields is the simpler act of binding an HTML attribute to your data. Alpine, again, provides a directive for this, x-bind
, as well a shortcut.
Given your data has a value for catPic
, we can bind it like so:
<img x-bind:src="catPic">
Code language: HTML, XML (xml)
Because this is something folks may use quite a bit, Alpine provides a shortcut:
<img :src="catPic2">
Code language: HTML, XML (xml)
I feel like a live embed of this would be gratuitous given how simple this is, but as it’s pictures of cats, sorry, you’re getting an embed:
Events
As you can probably guess by now, events are supported by a directive, this time the x-on
directive where you specify the event and the handler to call. As with attribute bindings, there’s a shortcut. Here’s a simple example:
<div x-data="{
catPic:'https://placecats.com/400/400',
flipImage() {
if (this.catPic.includes('/g')) this.catPic = this.catPic.replace('/g', '');
else this.catPic = this.catPic.replace('/400', '/g/400');
}
}">
<img :src="catPic" x-on:click="flipImage">
</div>
Code language: HTML, XML (xml)
I’ve defined a click handler on the image that runs a method, flipImage
. If you look up in the x-data
block, you can see I’ve defined the function there. In Alpine, your data can consist of various variables and methods interchangeably. Also note that flipImage
uses the this
scope to refer to the variable, catPic
. All in all, this flips out a static picture of a cat with a grayscale version.
The shorthand removes x-on:
and simply uses @
:
<img :src="catPic" @click="flipImage">
Code language: HTML, XML (xml)
You can play with this below:
Alpine also supports various modifies for event handling including the ability to run events once, prevent default behavior, throttle, and more. Check the modifiers docs for more examples.
Let’s Discuss the Smell…
I can still remember the first presentation I sat in discussing Alpine. I remember thinking: this looks really simple and practical, but there’s no way in heck I’m going to write a bunch of JavaScript code all inside an HTML attribute on a div
. Surely I thought, surely, the library’s not going to require me to do that?
Of course there is! To begin, you switch the x-data
directive from a block of variables and code to simply a name. That name can be anything, but I usually go with app
. If for some reason I had multiple unrelated Alpine blocks of code on one page I’d use something more descriptive, but app
is good enough:
<div x-data="app">
<img :src="catPic" @click="flipImage">
</div>
Code language: HTML, XML (xml)
In your JavaScript, you first listen for the alpine:init
event. This is an event thrown when Alpine itself is loaded, before it tries to interact with the page:
document.addEventListener("alpine:init", () => {
// stuff here...
});
Code language: JavaScript (javascript)
Then you can use Alpine.data
to initialize your application. Here’s the complete code block:
document.addEventListener("alpine:init", () => {
Alpine.data("app", () => ({
catPic: "https://placecats.com/400/400",
flipImage() {
if (this.catPic.includes("/g"))
this.catPic = this.catPic.replace("/g", "");
else this.catPic = this.catPic.replace("/400", "/g/400");
}
}));
});
Code language: JavaScript (javascript)
This is much cleaner and lets you keep your HTML and JavaScript separated as it should be. (IMO anyway!) You can see this version below:
Note: Notice I’m including the Alpine <script>
tag in the HTML instead of using CodePen’s JavaScript settings so you can clearly see it and so that I can add the defer
attribute.
With our logic now separated in code, it becomes easier to add new features. For example, by adding an init
function, Alpine will automatically run the method when the application is loaded. In the incredibly simple application below, the init
method is used to request a Dad Joke immediately:
When not to use Alpine
I just spent the last two thousand or so words explaining the basics of Alpine and raving about how much I love it so it would be crazy for me to tell you not to use it, right? Years ago, when I was much younger and foolish, I always reached for a JavaScript framework. First Angular, than Vue. Now that I’m much more mature and rarely make mistakes (ahem), my default is vanilla JavaScript, and by that I mean, no framework. If I just need a few lines of code, it would be silly to load anything I don’t need, even Alpine. That being said, when I’m building something that is doing a lot of DOM manipulation, needs proper two-way binding, or just feels like, mentally, I need an “assistant”, Alpine is what I go to first.
With that, let me leave you with not one, but two Alpine examples I’m particularly proud of. The first is IdletFleet, a simple “idle clicker” game where you work to build a space trading empire. Emphasis on the simple.
Next is Cat Herder, another “idle clicker” game but since it involves cats, you can’t be quite as idle.
Both games have links to their respective repositories where you can dig into the code and how Alpine helped, but I encourage you to play both a bit before you peek behind the curtains.
Also be sure to peruse the Alpine docs as I didn’t cover quite everything, but you can easily read the complete docs in less than an hour.
I like the introduction! “In the snow, uphill, both ways”, you forgot barefoot.
I did! 🙂
Hi, thanks for this blog post! Two unrelated things I’d like to note, hoping to help:
– where it says “Here’s the same example as above, re-written with x-if:”, then the following example actually is still the one with x-show
– malwarebytes notified that this site is running a potential malware, identified as iconicanhazdadjoke.com
“where it says “Here’s the same example as above, re-written with x-if:”, then the following example actually is still the one with x-show”
I reached out to the editor – should be fixed soon, thanks!
” malwarebytes notified that this site is running a potential malware, identified as iconicanhazdadjoke.com” eh… ok. 🙂 I’m just using the API so it should be fine.
“Same thing in “You can loop over an array of objects as well”: the following example is the previous one with strings.”
That was slightly misleading on my part, I just meant to say you can also do this, and here’s a CodePen of a loop example in general. Sorry for the confusion. Btw, if you would like to see that, ping me and I’ll share a quick CodePen!
Same thing in “You can loop over an array of objects as well”: the following example is the previous one with strings.
Regards!
Alpine is not the new JQuery because it isn’t CSP friendly.
Can you share some examples? I’ve not run into issues with this, but haven’t tested it heavily.
Alpine does document something in regards to this: https://alpinejs.dev/advanced/csp
Hi Ray, Alpine seems like a great lightweight alternative for handling simple interactivity without diving into larger frameworks. Have you found it more efficient than using vanilla JavaScript for smaller projects?
My “line” for when to use vanilla versus Alpine is a bit vague. In general, if I’m seeing a lot of dom manipulation, then I feel Alpine really helps. If I’m doing more than a few small things, I find Alpine can help me organize it.
I’ll try to describe a possible scenario:
I want to add a search field that does an Ajax call to an API and renders a list of results.
I’d probably just use Vanilla.
But now imagine my search has 1 or 2 more options (sort or filter, etc). And I want to update the URL dynamically so folks could bookmark the search. To me, this would kick it up a minor notch and make Alpine more appealing.
I don’t want to look at HTML tags to insert content in the DOM. It’s very hard to visually parse. I would much prefer something like mustache because it is much easier to see.
Understandable. 🙂