JavaScript Sprinkles with AlpineJS

I’m a big of ReactJS, but sometimes it is way too heavy for what I need. The majority of the applications I create are server-rendered and just need some “sprinkles” of JavaScript. In the past, I’ve leaned on jQuery for this, but I’m not a fan of the imperative nature of it.

Recently, I stumbled upon AlpineJS, which is a super minimalist framework that lets you write declarative sprinkles of JavaScript without resorting to something heavier like React or Vue. It comes in at just 6.4kb (compressed), less than a quarter of the size of Vue, React, or jQuery. And everything is done inline within your HTML, so, like React, your logic is co-located with your markup. Unlike React and Vue, AlpineJS does not use a virtual DOM and relies and the actual DOM.

So for a dead simple example, let’s say you wanted a button that toggled a piece of content. You use the x-data attribute to declare your component scope and initial data, x-show to toggle your content, and x-on:click to add the click behavior.

<div x-data="{isOpen: false}">
  <button x-on:click="isOpen = true">Show Content</button>
  <p x-show="isOpen">Content goes here.</p>
</div>

That’s it! Simple and easy to follow. If we wanted to add a little pizazz, we could use x-show.transition instead and this will animate our content when it’s shown and hidden.

My only real complaint with this, which is the same complaint I have about Angular (1.x) and Vue, is the placement of logic inside strings. The programming purist in me is not a fan of this, but I know it makes sense from a practical standpoint given the historical baggage of HTML.

Let’s move on to a slightly more complex example. Here we’ll keep the value of a text input in sync with the text in a <p> tag. The x-model attribute is used for input 2-way binding and x-text is used for outputting the variable.

<div x-data="{fullName: ''}">
  <label>Full Name</label>
  <input x-model="fullName" />
  <p x-text='fullName'></p>
</div>

But let’s say we want to prefix the name with “My name is” and only show it when the name has been filled in. We’ll use the x-if attribute on a <template> tag.

<div x-data="{fullName: ''}">
  <label>Full Name</label>
  <input x-model="fullName" />
  <template x-if="fullName.length > 0">
    <p>My name is <span x-text='fullName'></span></p>
  </template>
</div>

Lastly, let’s do about as complex of an example as I’d recommend with Alpine. We’ll loop over an array of users and display them in a table along with a form to add another user.

<div x-data="{newUser: { name: '', age: 10 }, users: [{name: 'John', age: 30}, {name: 'Mary', age: 40}]}">
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Age</th>
      </tr>
    </thead>
    <tbody>
      <template x-for="user in users" :key="users">
        <tr>
          <td x-text="user.name" />
          <td x-text="user.age" />
        </tr>
      </template>
    </tbody>
  </table>
  <div>
    <p>
      <label for='name'>Name</label>
      <input id='name' type='text' x-model='newUser.name' />
    </p>
    <p>
      <label for='age'>Age</label>
      <select id='age' x-model='newUser.age'>
        <option value='10'>10</option>
        <option value='20'>20</option>
        <option value='30'>30</option>
        <option value='40'>40</option>
        <option value='50'>50</option>
        <option value='60'>60</option>
        <option value='70'>70</option>
        <option value='80'>80</option>
        <option value='90'>90</option>
        <option value='100'>100</option>
      </select>
    </p>
    <p>
      <button x-on:click="users.push(newUser); newUser = {name: '', age: 10};">Add User</button>
    </p>
  </div>
</div>

Like I said, this is about as complex as you’d want to get with Alpine. But I think that’s a feature, not a bug. Sometimes when you use something as powerful as React or Vue, it’s tempting to push more and more of your app into that paradigm, all while forgetting that there are rough edges around doing so.