Alpine.js compared to Vue.js

Alpine.js calls itself A rugged, minimal framework for composing JavaScript behavior in your markup and asks you to think of it like Tailwind for JavaScript.

While I have little to no idea what the first phrase means I know what to think of the second, even if I've never used Tailwind CSS so far: stuff a lot of data into HTML attributes.

I'm fan of that! I'm fan of Unpoly and intercooler.js/htmx, and I love the approach of writing HTML and add some javascript behavior. Heck, I'm still a fan of jquery! There is nothing wrong with that.

So I set out to give Alpine.js a try and while I expected to work with server-side rendered HTML fragments, I ended up consuming JSON. In this regard, Alpine.js is closer to Vue.js (and it's not hiding the fact that some of the syntax is heavily borrowed from Vue) and therefore I just wrote a little thing one in Alpine.js and one in Vue.js, to compare them.

It has been a little bit of a challenge to understand the variable scope, since all the Alpine.js examples work with fixed JSON values and not dynamic data, fetched from an external data source. The one example that showcases fetch shows it directly used in an attribute, which is a little bit too much simplified.

        .then(response => response.json())
        .then(data => users = data)">
        <!-- -->

So how would I fetch() data on x-init and transform it?

For my experiment I decided to

  • pull a list of Github Gists,
  • parse the description Lepton - GitHub Gist Client style
  • save them into sessionStorage to get around the API rate limit while testing (60 calls per hour)
  • and just show them.

Bonus: use TailwindCSS.

The barebone HTML looks like this:

<div x-data="gistsData()" x-init="init()">
    <template x-for="gist in gists" :key="">
        <a x-bind:href="gist.html_url" x-text="gist.parsed.title"></a>

x-data declares the scope of the component, means all the data and methods you want to use in this component. In Vue, this is the data, methods and maybe computed fields.

x-init is a method that run on initialization or just some JSON. In this case it's a method, that fetches the data and saves the response in the gists key so it's accessible in the HTML.

function gistsData() {
  return {
    title: 'Latest Gists',
    gists: [],
    init() {
      // Testdata
        this.gists = [
          "id": "8f6af49ffe693c15faca67a7f3bf1a31",
          "html_url": "",
          "description": "[Lepton Title Style] Some description and <small class="hashtag text-monospace text-muted">#hash</small> <small class="hashtag text-monospace text-muted">#tags</small>"

      // get gists
        .then(response => response.json())
        .then(response => {
          this.gists = gists;

So this is the most basic example how to structure your code.

Check out the two codepens and compare them.


See the Pen Alpine.js fetch data on `x-init` by Marcus Obst (@localhorst) on CodePen.

💡 If you want to debug console.log(this.gists) it's pretty noisy. Checkout the Alpine.js Devtools extension, that is similar to the Vue Devtools.


See the Pen Vue.js fetch by Marcus Obst (@localhorst) on CodePen.

And Tailwind? In its most basic form, it's the Atomic CSS approach, similar to Bootstrap, down to some of the same class names, like mt-5 for margin-top: x;. Just add classes as if you would write inline css.

You can also ["compose" custom classes]( from those micro classes, almost as if you were writing CSS. :-o

I can see that this is fun to use, but it's not superior to other frameworks. Use whatever works for you.