I recently backpacked through Big Sur, and after a few days, the inevitable happened: I looked at everything I carried and demanded it justify its presence in my backpack. Making tech choices during the software development process is similar. Every asset in the system adds complexity, so everything better be pulling its weight.
Alpine has carved out a place for itself as the minimalist choice among reactive frameworks. It offers an impressive range of powers within a tight footprint. It’s surprising how much you can do with such a small feature set.
Alpine’s minimalist API
As described in the Alpine docs, Alpine is a collection of 15 attributes, six properties, and two methods. That’s a very small API. It delivers reactivity in a simple package, then offers a few niceties on top like eventing and a store.
Consider the following simple web page:
Besides including the Alpine package via CDN, the only Alpine-related things here are the two directives: x-data
and x-text
.
If you put this into an HTML page on your system and view it in the browser, you’ll see the message: “Text literal” output. This is not terribly impressive, but it demonstrates two interesting facts about Alpine.
First, for reactivity to engage, you must enclose the markup in an x-data
directive. If you remove this directive, the x-text
will not take effect. So, the x-data
directive creates an Alpine component. In this case, the directive is empty, but in real usage you almost always have data in there; after all, you’re writing components whose purpose is to be reactive to that data.
Second, you can put any valid JavaScript into the x-text
. This is true of all Alpine directives. The x-text
property gives you a link between the HTML (the view) and the JavaScript (the behavior).
Using the x-data and x-text elements
The x-data
contents are provided to all the contained elements. To understand what I mean, look at the following code:
Now the page will output the beginning of the Declaration of Independence. You can see that x-data
has defined a plain old JavaScript object with a single field, “message,” containing the preamble, and that the x-text
refers to this object field.
Reactivity in Alpine
Now we’ll use reactivity to fix up an error in the document:
As should now be evident, the x-text
directive refers to the noun variable exposed by the x-data
directive. The new piece here is the button, which has an x-on:click
directive. The handler for this click event replaces the old default noun (“men”) with a gender-neutral one, “people.” Reactivity then handles updating the reference in the x-text
.
The UI will automatically reflect the change to the data.
Functions in data
The data properties in Alpine are full-featured JavaScript objects. Knowing that, here’s another way to handle the above requirement:
In this example, you can see that the data
object now hosts a fixIt
method that is called by the click handler. We can craft whatever object structure is best suited to the behavior we want to see in the HTML.
Fetching remote data
Now let’s switch gears and think about a requirement where you want to load a JSON-formatted list of the American presidents from an external API. The first thing we’ll do is load it when the page loads. For that, we’ll use the x-init
directive:
{
const response = await fetch('https://raw.githubusercontent.com/hitch17/sample-data/master/presidents.json');
presidents = await response.json();
}
)">
Let’s unpack this code. The x-data
directive should be clear; it simply has a presidents
field with an empty array. The x-text
in the span
element outputs the contents of this field.
The x-init
code is a bit more involved. First off, notice that it is wrapped in a self-executing function; this is because Alpine expects a function (not a function definition). If you were to use the non-async callback form of fetch
, you don’t need to wrap the function like this (because you don’t require the async-scoped function in that case).
Once the list of presidents is obtained from the endpoint, we stick it into the presidents
variable, which Alpine has exposed to us as part of the x-data
object.
To reiterate: Alpine is making the data from x-data
available to the other directive functions (like x-init
) within the same context.
Iterating with Alpine
At this point, the app is pulling the data from the remote endpoint and saving it into the state; however, it is outputting something like [Object],[Object]....
. That is not what we want. To fix it, we need to first get a look at iterating over the data:
-
From: Until:
Man, that is really clean, self-explanatory code and template!
The code contains a normal un-ordered list, and then an HTML template element, which contains an x-for
directive. This directive operates just like it does in other reactive frameworks. It allows specifying a collection, presidents, and an identifier, which will be provided to the enclosed markup representing each instance of that collection (in this case, pres
).
The rest of the markup makes use of the pres
variable to output data from the objects via x-text
. (This use of iterator is one of the most prevalent patterns in all of software, by the way.)
The app now looks something like the screenshot below, showing a list of United States presidents.
Show/Hide and onClick
Now let’s say we want to add the ability to toggle the data for a president by clicking on the president’s name. We modify the markup to look like this:
From: Until:
We use the x-show
directive on a div
containing the presidential details. The truthiness of the x-show
value determines if the content is visible. In our case, that is determined by pres.show
field. (Note that in a real application, you might not want to use the actual business data to host the show/hide variable, to keep data and behavior more isolated.)
To change the value of pres.show
we add an x-on:click
handler to the header. This handler simply swaps the true/false value of pres.show: pres.show = ! pres.show
.
Add transition animation
Alpine includes built-in transitions that you can apply to the show/hide feature. Here’s how to add the default animation:
From: Until:
All that changed was the element bearing the x-show
directive, which now also has an x-transition
directive. By default, Alpine applies sensible transitions. In this case, a slide and fade effect is used. You can customize the transition extensively, including by applying your own CSS classes to various stages of the animation. See theAlpine transition docs for more info.
Binding to inputs
Now we’ll add a simple filter capability. This will require adding an input that you bind to your data, then filtering the returned dataset based on that value. You can see the changes here:
pres.president.includes(this.filter) )
}
}"
...
...
Notice the x-data
object now has a “filter” field on it. This is two-way bound to the input element via the x-model
directive which points to “filter
“.
We’ve changed the template x-for
directive to reference a new getPresidents()
method, which is implemented on the x-data
object. This method uses standard JavaScript syntax to filter the presidents based on whether they include the text in the filter field.
See my GitHub repository to view all the code for examples in this article.
Conclusion
Like its namesake, Alpine is a backpack with the basic gear to get you through the mountains. It is minimal, but sufficient. It does include some higher-level features, such as a central store and an eventing system, as well as a plugin architecture and ecosystem.
In all, Alpine is ergonomic to use and will be familiar if you’ve worked with other reactive frameworks. For these reasons, it’s quick and easy to learn. The simplicity of declaring a component and its data in an x-data
directive is simply genius. Alpine will be a tempting option the next time I go code venturing.
See my JavaScript framework comparison for more about Alpine and other front-end frameworks.