We have big JavaScript frameworks that tons of people already use and like, including React, Vue, Angular, and Svelte. Do we need another JavaScript library? Let’s take a look at Alpine.js and you can decide for yourself. Alpine.js is for developers who aren’t looking to build a single page application (SPA). It’s lightweight (~7kB gzipped) and designed to write markup-driven client-side JavaScript.
The syntax is borrowed from Vue and Angular directive. That means it will feel familiar if you’ve worked with those before. But, again, Alpine.js is not designed to build SPAs, but rather enhance your templates with a little bit of JavaScript.
For example, here’s an Alpine.js demo of an interactive “alert” component.
The alert message is two-way bound to the input using x-model="msg"
. The “level” of the alert message is set using a reactive level
property. The alert displays when when both msg
and level
have a value.
It’s like a replacement for jQuery and JavaScript, but with declarative rendering
Alpine.js is a Vue template-flavored replacement for jQuery and vanilla JavaScript rather than a React/Vue/Svelte/WhateverFramework competitor.
Since Alpine.js is less than a year old, it can make assumptions about DOM APIs that jQuery cannot. Let’s briefly draw a comparison between the two.
Querying vs. binding
The bulk of jQuery’s size and features comes in the shape of a cross-browser compatibility layer over imperative DOM APIs — this is usually referred to as jQuery Core and sports features that can query the DOM and manipulate it.
The Alpine.js answer to jQuery core is a declarative way to bind data to the DOM using the x-bind
attribute binding directive. It can be used to bind any attribute to reactive data on the Alpine.js component. Alpine.js, like its declarative view library contemporaries (React, Vue), provides x-ref
as an escape hatch to directly access DOM elements from JavaScript component code when binding is not sufficient (eg. when integrating a third-party library that needs to be passed a DOM Node).
Handling events
jQuery also provides a way to handle, create and trigger events. Alpine.js provides the x-on
directive and the $event
magic value which allows JavaScript functions to handle events. To trigger (custom) events, Alpine.js provides the $dispatch
magic property which is a thin wrapper over the browser’s Event and Dispatch Event APIs.
Effects
One of jQuery’s key features is its effects, or rather, it’s ability to write easy animations. Where we might use slideUp
, slideDown
, fadeIn
, fadeOut
properties in jQuery to create effects, Alpine.js provides a set of x-transition
directives, which add and remove classes throughout the element’s transition. That’s largely inspired by the Vue Transition API.
Also, jQuery’s Ajax client has no prescriptive solution in Alpine.js, thanks to the Fetch API or taking advantage of a third party HTTP library (e.g. axios, ky, superagent).
Plugins
It’s also worth calling out jQuery plugins. There is no comparison to that (yet) in the Alpine.js ecosystem. Sharing Alpine.js components is relatively simple, usually requiring a simple case of copy and paste. The JavaScript in Alpine.js components are “just functions” and tend not to access Alpine.js itself, making them relatively straightforward to share by including them on different pages with a script
tag. Any magic properties are added when Alpine initializes or is passed into bindings, like $event
in x-on
bindings.
There are currently no examples of Alpine.js extensions, although there are a few issues and pull requests to add “core” events that hook into Alpine.js from other libraries. There are also discussions happening about the ability to add custom directives. The stance from Alpine.js creator Caleb Porzio, seems to be basing API decisions on the Vue APIs, so I would expect that any future extension point would be inspired on what Vue.js provides.
Size
Alpine.js is lighter weight than jQuery, coming in at 21.9kB minified — 7.1kB gzipped — compared to jQuery at 87.6kB minified — 30.4kB minified and gzipped. Only 23% the size!
Most of that is likely due to the way Alpine.js focuses on providing a declarative API for the DOM (e.g. attribute binding, event listeners and transitions).
For the sake of comparison, Vue comes in at 63.5kB minified (22.8kB gzipped). How can Alpine.js come in lighter despite it’s API being equivalent Vue? Alpine.js does not implement a Virtual DOM. Instead, it directly mutates the DOM while exposing the same declarative API as Vue.
Let’s look at an example
Alpine is compact because since application code is declarative in nature, and is declared via templates. For example, here’s a Pokemon search page using Alpine.js:
This example shows how a component is set up using x-data
and a function that returns the initial component data, methods, and x-init
to run that function on load.
Bindings and event listeners in Alpine.js with a syntax that’s strikingly similar to Vue templates.
- Alpine:
x-bind:attribute="express"
andx-on:eventName="expression"
, shorthand is:attribute="expression"
and@eventName="expression"
respectively - Vue:
v-bind:attribute="express"
andv-on:eventName="expression"
, shorthand is:attribute="expression"
and@eventName="expression"
respectively
Rendering lists is achieved with x-for
on a template
element and conditional rendering with x-if
on a template
element.
Notice that Alpine.js doesn’t provide a full templating language, so there’s no interpolation syntax (e.g. {{ myValue }}
in Vue.js, Handlebars and AngularJS). Instead, binding dynamic content is done with the x-text
and x-html
directives (which map directly to underlying calls to Node.innerText
and Node.innerHTML
).
An equivalent example using jQuery is an exercise you’re welcome to take on, but the classic style includes several steps:
- Imperatively bind to the button click using
$('button').click(/* callback */)
. - Within this “click callback” get the input value from the DOM, then use it to call the API.
- Once the call has completed, the DOM is updated with new nodes generated from the API response.
If you’re interested in a side by side comparison of the same code in jQuery and Alpine.js, Alex Justesen created the same character counter in jQuery and in Alpine.js.
Back in vogue: HTML-centric tools
Alpine.js takes inspiration from TailwindCSS. The Alpine.js introduction on the repository is as “Tailwind for JavaScript.”
Why is that important?
One of Tailwind’s selling points is that it “provides low-level utility classes that let you build completely custom designs without ever leaving your HTML.” That’s exactly what Alpine does. It works inside HTML so there is no need to work inside of JavaScript templates the way we would in Vue or React Many of the Alpine examples cited in the community don’t even use script tags at all!
Let’s look at one more example to drive the difference home. Here’s is an accessible navigation menu in Alpine.js that uses no script tags whatsoever.
This example leverages aria-labelledby
and aria-controls
outside of Alpine.js (with id
references). Alpine.js makes sure the “toggle” element (which is a button), has an aria-expanded
attribute that’s true
when the navigation is expanded, and false
when it’s collapsed. This aria-expanded
binding is also applied to the menu itself and we show/hide the list of links in it by binding to hidden
.
Being markup-centric means that Alpine.js and TailwindCSS examples are easy to share. All it takes is a copy-paste into HTML that is also running Alpine.js/TailwindCSS. No crazy directories full of templates that compile and render into HTML!
Since HTML is a fundamental building block of the web, it means that Alpine.js is ideal for augmenting server-rendered (Laravel, Rails, Django) or static sites (Hugo, Hexo, Jekyll). Integrating data with this sort of tooling can be a simple as outputting some JSON into the x-data="{}"
binding. The affordance of passing some JSON from your backend/static site template straight into the Alpine.js component avoids building “yet another API endpoint” that simply serves a snippet of data required by a JavaScript widget.
Client-side without the build step
Alpine.js is designed to be used as a direct script include from a public CDN. Its developer experience is tailored for that. That’s why it makes for a great jQuery comparison and replacement: it’s dropped in and eliminates a build step.
While it’s not traditionally used this way, the bundled version of Vue can be linked up directly. Sarah Drasner has an excellent write-up showing examples of jQuery substituted with Vue. However, if you use Vue without a build step, you’re actively opting out of:
- the Vue CLI
- single file components
- smaller/more optimized bundles
- a strict CSP (Content Security Policy) since Vue inline templates evaluate expressions client-side
So, yes, while Vue boasts a buildless implementation, its developer experience is really depedent on the Vue CLI. That could be said about Create React App for React, and the Angular CLI. Going build-less strips those frameworks of their best qualities.
There you have it! Alpine.js is a modern, CDN-first library that brings declarative rendering for a small payload — all without the build step and templates that other frameworks require. The result is an HTML-centric approach that not only resembles a modern-day jQuery but is a great substitute for it as well.
If you’re looking for a jQuery replacement that’s not going to force you into a SPAs architecture, then give Alpine.js a go! Interested? You can find out more on Alpine.js Weekly, a free weekly roundup of Alpine.js news and articles.