Editor’s note: This article was updated on 31 March 2022 to include information about the latest release of Alpine.
Alpine is a rugged, minimal frontend development framework for adding JavaScript behavior to HTML markups. It enables you to harness the reactive and declarative nature of popular frontend libraries and frameworks such as Angular, React, and Vue, at a much lower cost.
There is no build step and the library file size is about 4KB gzipped. Alpine is not meant to replace frameworks such as Vue and React; if you have a highly interactive single-page app, it’s best to stick to more powerful tools. It’s best used when your project requires only minimal JavaScript, such as when you only need one or two components, like dropdowns, sidebars, tabs, and image selection.
Alpine is also great for server-side rendered apps, such as Laravel, Rails, and AdonisJS, which require you to toggle some JavaScript components. And since it doesn’t have a virtual DOM, it’s easier to set up.
Essentially, Alpine is like Tailwind for JavaScript. The DOM and behavior are not separated; you get to keep your DOM and sprinkle in behavior as you see fit. You can easily write declarative code as opposed to procedural code. Finally, Alpine has a very small footprint in your application.
Now, let’s move on to installation steps and get our hands dirty with Alpine.
Adding Alpine to a project is easy. You can either include it from a script
tag through a CDN or import it as a module.
script
tagUsing a script
tag is the easiest and most straightforward way to to add Alpine to your project. You just need to add the snippet below at the end of the <head>
section of your HTML file:
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
Specifying the version as
@3.x.x
in the CDN will pull the latest version of Alpine v3. However, in production, it’s recommended to hardcode the latest version in the CDN link.
With this method, you first need to install Alpine via npm:
npm install alpinejs
Then, import Alpine into your bundle and initialize it:
import Alpine from 'alpinejs' // optional window.Alpine = Alpine // initialize Alpine Alpine.start()
<html> <head> <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script> </head> <body> <div x-data="{ isOpen: true }"> <button x-on:click=" isOpen = !isOpen">Toggle</button> <h1 x-show="isOpen">Alpinjs</h1> </div> </body> </html>
The first step to using Alpine is to define a state. The state goes wherever it is needed and has the same scope as the HTML selector you put in.
In the code above, we defined a scope using the x-data
directive by passing in an object as the value of the state. The x-on
directive listens for events. For the button
element, we’re listening to the click event, which changes the value of isOpen
to true
or false
. The x-show
directive shows or hides an element from the DOM depending on the value of the state object’s isOpen
property.
At the core of the Alpine framework are directives, which change the DOM layout by adding and removing DOM elements, and alter the behavior and appearance of elements in the DOM. Alpine directives starts with a x-
followed by the name of the directive.
Let’s quickly go over some of the directives and see how they can be used.
x-data
x-data
initializes a new component scope with an object in an HTML element. All child HTML elements have access to the data object that exists in its parent element:
<div x-data="{ isOpen: false }">...</div>
x-init
x-init
is used to run an expression when a component initializes, and can be used in to set the initial value of the component state:
<div x-data="{ title: 'foo' }" x-init="title = 'bar'"></div>
Also, x-init
can be used to run code after a component initializes by passing a callback function:
<div x-data="{ images: [] }" x-init="$nextTick(() => { fetch('https://pixabay.com/api/?key=15819227-ef2d84d1681b9442aaa9755b8&q=yellow+flowers&image_type=photo') .then(response => response.json()) .then(response => { images = response.hits }) })" ></div>
x-bind
Alpine provides x-bind
as a mechanism for binding value, boolean, and class attributes.
Value attribute binding:
<img x-bind:src="imgSrc">
This sets the value of an attribute to the result of the expression.
Class attribute binding:
<div x-bind:class="{ 'hidden': isClosed }"></div>
For class binding, an object expression is passed. The object keys are class names, and the values are boolean expressions. If the boolean expression evaluates to true
, the class name will be applied to that element.
Boolean attribute binding:
<input type="text" x-bind:hidden="true">
Boolean binding works the same way as attribute binding, but the expression passed has to evaluate to true
or false
.
x-on
x-on
adds an event listener to the element on which it’s declared. When the element emits that event (e.g., a click or input event), an expression (or function) will be executed:
<button x-on:click="foo = 'bar'">Click me</button>
x-show
x-show
changes the CSS display property of the element depending on whether the expression evaluates to true
or false
. If the expression evaluates to false
, the element’s display property is set to none
. If it resolves to true
, the display property is set to block
:
<div x-show="isOpen"></div>
x-if
While x-show
can be used to toggle the display property of an element, the element is actually not removed from the DOM. The x-if
directive doesn’t hide elements with CSS; it adds and removes them entirely from the DOM.
The value of x-if
is a boolean expression that can evaluate to true
or false
. If the expression evaluates to false
, x-if
removes its host element from the DOM. x-if
only works within the template
element and must have a single element root inside the template
tag:
<template x-if="true"> <div>...</div> </template>
x-for
x-for
helps when you want to create new DOM nodes for each item in a collection. Just like the x-if
directive, the x-for
directive needs to exist on a template
tag, not a regular DOM element:
<template x-for="item in items" :key="item"> <div x-text="item"></div> </template>
x-model
x-model
adds a two-way data binding capability to an element, and synchronizes the value of an input element and the component data. It is smart enough to detect changes on text inputs, checkboxes, radio buttons, text areas, and multiple selects, and binds their value to the component data:
<input type="search" x-model="search">
x-text
While x-bind
is for attribute binding, x-text
is used to set the value of an element’s innerText
:
<span x-text="title"></span>
x-html
x-html
works similarly to x-text
, but instead of setting the innerText
, it sets the value of the innerHTML
of an element:
<span x-html="title"></span>
To learn more about Alpine directives, be sure to check out the docs.
To demonstrate how these directives can be used together, let’s build a simple image gallery:
<!DOCTYPE html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" /> <!-- Custom CSS --> <link rel="stylesheet" href="css/custom.css" /> <!-- Fonts --> <link href="https://fonts.googleapis.com/css?family=Lora:400,700|Nunito:400,700" rel="stylesheet" /> <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script> <script defer src="https://use.fontawesome.com/releases/v5.0.7/js/all.js" ></script> <link href="images/favicon_32.ico" rel="shortcut icon" type="image/x-icon" /> <link href="images/favicon_256.ico" rel="apple-touch-icon" /> </head> <body x-data="images()" x-init="fetch('https://pixabay.com/api/?key=15819227-ef2d84d1681b9442aaa9755b8&q=yellow+flowers&image_type=photo') .then(response => response.json()) .then(response => { images = response.hits })" > <!-- Header section --> <header class="navigation"> <div class="container navigation-content"> <nav class="navbar navbar-expand-lg navbar-light"> <a class="navbar-brand" href="index.html" ><img src="https://godwinekuma.github.io/we-connect/images/logo-white.svg" alt="weconnect logo" height="50" class="navbar-brand-image" /> PictureGram</a > <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" > <span class="navbar-toggler-icon"></span> </button> </nav> </div> </header> <!-- Header section /--> <!-- Hero Section --> <div> <section class="hero"> <div class="container"> <div class="d-flex flex-column align-items-center"> <h1 class="display-4" style="text-align:center"> Search for images. </h1> <h2 class="" style="text-align:center"> Pixel perfect images can be found here. </h2> <div class="input-group"> <input type="text" class="form-control" placeholder="search images" x-model="q" aria-label="Text input with segmented dropdown button" /> <select class="custom-select" x-model="image_type"> <option selected>choose image type</option> <option value="all">All</option> <option value="photo">Photo</option> <option value="illustration">Illustration</option> <option value="vector">Vector</option> </select> <div class="input-group-append"> <button class="btn btn-primary" type="button" x-on:click="getImages()" > Search </button> </div> </div> </div> </div> </section> <section id="photos" class="my-5"> <template x-for="image in images" :key="image.id"> <img x-bind:src="image.webformatURL" alt="image.tags[0]" /> </template> </section> </div> <script> function images() { return { images: [], q: "", image_type: "", getImages: async function() { console.log("params", this.q, this.image_type); const response = await fetch( `https://pixabay.com/api/?key=15819227-ef2d84d1681b9442aaa9755b8&q=${ this.q }&image_type=${this.image_type}` ); const data = await response.json(); this.images = data.hits; } }; } </script> </body> </html>
Our gallery app gets a list of images from Pixabay and displays them. The application state is set on the body
tag by the x-data
directive using a function called images
. The function returns an object that contains image
, q
, image-type
, and getImages
.
The initial value of an image is set using the x-init
directive. The x-init
fetches a list of images from Pixabay and sets it as the value of images
field. q
captures the value of the <input>
and is set using the x-model
directive. image_type
, on the other hand, captures the value of the <select></select>
and is also set using the x-model
directive.
We attached a click event listener to the <button>
. When the button is clicked, the getImages()
method in the state is called. The getImages()
method fetches new images based on the value of q
and image_type
.
Alpinejs_Tutorial
No Description
In this tutorial, we covered how to use Alpine and built a sample image gallery component with the framework. Though it might not totally replace other frameworks, it can be used in combination with React or Vue to quickly prototype components without writing much JavaScript. To learn more about Alpine, be sure to check out the framework’s website.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
2 Replies to "Getting started with Alpine"
Nested x-for are only possible with a later version of AlpineJS than is used in this example.
Tested on IE11?