Why Alpine is the new jQuery and Why that is an Awesome Thing

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 spans 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:

<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)

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.

It's time to take your JavaScript to the next level

2 responses to “Why Alpine is the new jQuery and Why that is an Awesome Thing”

  1. Latz says:

    I like the introduction! “In the snow, uphill, both ways”, you forgot barefoot.

Leave a Reply

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

Did you know?

Frontend Masters Donates to open source projects. $313,806 contributed to date.