Digging into Alpine.js

Raymond Camden

Senior Web Developer ··Website Tips

Digging into Alpine.js

I’ve been lucky to have the privilege of using JavaScript since it’s inception, and while I feel like one day I may be an expert, I’m just happy to see the language, and the web platform as a whole, continue to evolve. That being said, I’m seeing fewer and fewer people talking about simpler uses of JavaScript, specifically progressive enhancement of web pages. Most of the discussion in the JavaScript space centers on the assumption you’re building a SPA (Single Page Application), and the idea that you may simply want to enhance a page for improved usability feels forgotten.

This is why - for a long time - I was a huge fan of Vue.js. While Vue absolutely supported large scale SPA environments, it was also very well suited for smaller more simpler deployments. Vue.js 3, an all around dramatic improvement for the project, no longer feels well suited for smaller use cases however. That’s truly my personal opinion so take of it what you will, but I’ve been reconsidering lately what I’ll use for progressive enhancement use-cases.

Imagine my surprise when I was recently introduced to Alpine.js.

Alpine.js homepage

Alpine touts itself as “jQuery for the modern web” and from my experience with it, this is an incredibly accurate description. It’s got a minimal but powerful set of features focused on:

  • Helping you dynamically render HTML based on your data

  • Binding form fields and data

  • Easily adding event handlers

And while there’s more, that short list covers the majority of what you’ll be building with Alpine. The Alpine docs do a great job of introducing you to the library, but let me walk you through the basics and demonstrate how quick it is to start using Alpine.

Installing and Using Alpine


While you can install Alpine via npm (npm install alpinejs), the simplest way is to drop in a script tag in the head of your HTML page:

<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>

The 3.x.x used in the URL above locks the library to the current major version which is definitely recommended. (I speak from personal experience when a number of CodePens I had using a CDN for Vue broke when they switched from loading 2.x to 3.x. That was definitely my fault however!)

The next step involves figures out where you want Alpine to work. Much like when you use Vue to progressive enhance a page, Alpine requires you to “mark” the area it will be involved in. So for example, perhaps you have a form that you want to enhance with custom validation or perhaps you’re building an Ajax-driven search field. Once you figure out where in your page you’ll be doing “dynamic stuff”, you let Alpine know by adding the x-data attribute. So for example:

<!-- Let's do cool stuff here! -->
<div x-data="">
    <!-- more stuff here... -->
</div>

Alpine Template Directives


Now let’s actually do stuff. The x-data attribute used earlier can define variables we use within our Alpine application. So for example, let’s define a name and age:

<!-- Let's do cool stuff here! -->
<div x-data="{
    name:'Raymond Camden',
    age:49
        }">
    <!-- more stuff here... -->
</div>

We’ve defined data, but how do we use it? There’s multiple template options that can be used here. First, x-text will simply render the value out. Here’s an example:

<div x-data="{
        name:'Raymond Camden',
        age:49
        }">
    
    <p>
        My name is <span x-text="name"></span> and I'm 
        <span x-text="age"></span> years old.
    </p>
</div>

Along with x-text, you can use x-html. Separating the two directives makes it a bit safer as it reduces the chance of accidentally including invalid or insecure HTML into the DOM. Let’s add a bio value and render it.

<div x-data="{
        name:'Raymond Camden',
        age:49,
        bio:'I am truly <strong>cool</strong>.'
        }">
    
    <p>
        My name is <span x-text="name"></span> and I'm 
        <span x-text="age"></span> years old.
    </p>
    <p x-html="bio"></p>
</div>

The result:

Screen shot of rendered Alpine application

There’s other directives for displaying data of course. Conditions can be done with either x-if or x-show. The x-if directive completely adds or removes content from the DOM whereas x-show will use CSS to hide something. In both cases, it takes an expression that should evaluate to true. Let’s add a new value for coolness and check for it in our document.

Both directives should be used in an <template> directive, not the actual item you want to show or hide. Also note there is not a x-else directive, but you can use another directive with the opposite logic to get the same result. Here’s an example:

<div x-data="{
        name:'Raymond Camden',
        age:49,
        bio:'I am truly <strong>cool</strong>.',
        cool:false
        }">
    
    <p>
        My name is <span x-text="name"></span> and I'm 
        <span x-text="age"></span> years old.
    </p>
    <p x-html="bio"></p>
    <template x-if="cool">
    <p>
        Yes, you are cool!
    </p>
    </template>
    <template x-if="!cool">
    <p>
        Sorry, you aren't as cool as you think you are.
    </p>
    </template>
</div>

You’ll notice I first check if cool is true and then check if it is not true (!cool). You can test this yourself and simply modify that cool value to see the results.

Lastly, you can loop over arrays of data using the x-for directive. Here’s an example where we’ve added an array of cats and then render it in a bulleted list. Like the previous example, we must use the <template> tag again.

<div x-data="{
        name:'Raymond Camden',
        age:49,
        bio:'I am truly <strong>cool</strong>.',
        cool:false,
        cats: ['Luna', 'Pig', 'Elise' ]
        }">
    
    <p>
        My name is <span x-text="name"></span> and I'm 
        <span x-text="age"></span> years old.
    </p>
    <p x-html="bio"></p>
    <template x-if="cool">
    <p>
        Yes, you are cool!
    </p>
    </template>
    <template x-if="!cool">
    <p>
        Sorry, you aren't as cool as you think you are.
    </p>
    </template>
    <h2>My Cats</h2>
    <ul>
        <template x-for="cat in cats">
            <li x-text="cat"></li>
        </template>
    </ul>
</div>

Now we’re getting somewhere…

Updated screenshot showing output of demo

Data and Two Way Binding


Up until this point, we’ve simply rendered the data given to us. But Alpine also supports two way binding. This can be used to connect form fields with data in either direction. To bind a form element, you use x-model. Let’s add fields for our name and coolness factor:

<div x-data="{
        name:'Raymond Camden',
        age:49,
        bio:'I am truly <strong>cool</strong>.',
        cool:false,
        cats: ['Luna', 'Pig', 'Elise' ]
        }">
    
    <p>
        My name is <span x-text="name"></span> and I'm 
        <span x-text="age"></span> years old.
    </p>
    <p x-html="bio"></p>
    <template x-if="cool">
    <p>
        Yes, you are cool!
    </p>
    </template>
    <template x-if="!cool">
    <p>
        Sorry, you aren't as cool as you think you are.
    </p>
    </template>
    <h2>My Cats</h2>
    <ul>
        <template x-for="cat in cats">
            <li x-text="cat"></li>
        </template>
    </ul>
    
    <p>
        New Name: <input x-model="name">
    </p>
    <p>
        Am I cool? 
        <select x-model="cool">
            <option value="true">Yes</option>
            <option value="false">No</option>
        </select>
    </p>
</div>

Now you can edit either form field and see the HTML above change in real time.

Updated demo with two form fields

While the previous demo shows an input and select field, note that you can use it with any form field, including checkboxes and radio buttons.

Handling Events the Alpine Way


Alpine provides event handling support via x-on but like Vue, provides a shortcut with the @ directive. You supply the name of the event you care to handle and the code to run when the event fires:

button x-on:click="handleButton">Click Me</button>

Or:

<button @click="handleButton">Click Me</button>

Now when clicked, Alpine will run the function handleButton, but where is that defined? Why back in our x-data of course. Here’s our updated parent div:

<div x-data="{
        name:'Raymond Camden',
        age:49,
        bio:'I am truly <strong>cool</strong>.',
        cool:false,
        cats: ['Luna', 'Pig', 'Elise' ],
        handleButton() {
            alert('Button clicked');
        }
}">

Alpine supports multiple modifies for event handling. So for example, a typical use case is to prevent a form from submitting when clicking the submit button. This can be done by adding .prevent, for example:

<input type="submit" @click.prevent="handleFormat">

Check the modifiers docs for a full list of these ways of handling events.

Binding HTML Attributes via Data


Earlier you saw how x-model let you bind form fields to data, but what about other attributes? By using x-bind, you can bind arbitrary HTML attributes to your data. As with events, there’s also a shorthand, :.

So for example, let’s add an image URL:

<div x-data="{
        name:'Raymond Camden',
        age:49,
        bio:'I am truly <strong>cool</strong>.',
        cool:false,
        cats: ['Luna', 'Pig', 'Elise' ],
        handleButton() {
            alert('Button clicked');
        }, 
        catImage: 'https://placekitten.com/500/500'
}">

We can then use it via:

<img x-bind:src="catImage">

Or:

<img :src="catImage">

Demo showing dynamic image

Wow that div is getting out of hand!


At this point, you’re probably thinking much the same as I did… exactly how much stuff can we shove into that div tag? While I appreciate the simplicity of that approach, I think many developers will eventually want to move their variables and functions out of their HTML and into a JavaScript file or script block on the page. How is that done?

First off, the value passed to x-data can be a simple name, like mydata. Next, in a separate JavaScript, or just a script block, first define an event listener for Alpine’s initialization:

document.addEventListener('alpine:init', () => {

});
Next, we can use the Alpine.data method to define our data:
document.addEventListener('alpine:init', () => {
    
    Alpine.data('mydata', () => ({
        name:'Ray' 
    }))

});

In this case mydata just has a variable, name. Just as before, you can include multiple values and functions, so for example:

document.addEventListener('alpine:init', () => {
    
    Alpine.data('mydata', () => ({
        name:'Ray',
        loud(s) {
            return s.toUpperCase()
        }
    }))

});

Back in our HTML, everything inside the div works as before:

<div x-data="mydata">
    Hello, <span x-text="name"></span>, 
    my loud name is <span x-text="loud(name)"></span>
</div>

While you aren’t required to use one way of the other, I’d imagine most developers will probably default to defining their data in a JavaScript block, unless their code is very simple.

We can further enhance our code by making use of the init method. In a script-defined Alpine set of data, if we include an init function, it will automatically run. For example:

document.addEventListener('alpine:init', () => {
    Alpine.data('swData', () => ({
        results:null,
        async init() {
            let data = await fetch('http://swapi.dev/api/films');
            this.results = (await data.json()).results;
        }
    }))

});

In this block, the init function is called automatically and will fetch a list of films from the Star Wars API. The result is stored to an array called results which can then be used in HTML:

<div x-data="swData">

    <template x-if="results">
        <ul>
        <template x-for="result in results">
            <li x-text="result.title"></li>
        </template>
        </ul>
    </template>

</div>

If developers prefer, they can alternatively use an x-init directive on their parent div tag.

Screen shot of Alpine app showing a list of dynamically loaded films

What’s Next?


This was a rather quick introduction to Alpine, but hopefully you’ve gotten a feel for it’s simplicity and usefulness. Definitely take some time to peruse the docs(it won’t take you long), and give it a try yourself!

Frequently Asked Questions


Do I need to know how to code in order to use WordPress?

There isn’t a need for advanced coding knowledge to use WordPress. The platform offers plenty of plugins and themes which you can use to customize your website.

Can you migrate my existing website over?

Yes, and without issue. Regardless of how many websites you’re managing, we’ll bring all of them under the Verpex wing free of charge. Just send us the details of your websites when you’ve signed up to get started.

What sort of websites can I run on VPS and dedicated servers?

Since both are highly customizable, you can run just about any type of site you’d like. However, we’d generally recommended only larger organizations consider these hosting types.

What are the hosting options with a website builder?

Most website builders offer a free plan with a free domain, but your name will go after the company's name. To get any address you like, you will need to purchase the domain name on your own.

Raymond Camden
About the Author
Raymond Camden

Raymond Camden is a Senior Developer Evangelist for Adobe. He works on the Document Services APIs to build powerful (and typically cat-related) PDF demos. He is the author of multiple books on web development and has been actively blogging and presenting for almost twenty years. Raymond can be reached at his blog www.raymondcamden.com and social media.

View all posts by Raymond Camden
Jivo Live Chat