Godwin Ekuma I learn so that I can solve problems.

Getting started with Alpine

7 min read 2108

Getting Started With Alpine.js

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.


Installation and basic use

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.

From script tag

Using 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.

As a module

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

A basic component in Alpine

    <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
    <div x-data="{ isOpen: true }">
      <button x-on:click=" isOpen = !isOpen">Toggle</button>
      <h1 x-show="isOpen">Alpinjs</h1>

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.

Alpine directives

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 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 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(() => {
      .then(response => response.json())
      .then(response => { images = response.hits })


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 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 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>


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">


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>


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">


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 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">
    <!-- Required meta tags -->
    <meta charset="utf-8" />
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    <!-- Bootstrap CSS -->
    <!-- Custom CSS -->
    <link rel="stylesheet" href="css/custom.css" />
    <!-- Fonts -->
    <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
      rel="shortcut icon"
    <link href="images/favicon_256.ico" rel="apple-touch-icon" />
.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"
              alt="weconnect logo"
            aria-label="Toggle navigation"
            <span class="navbar-toggler-icon"></span>
    <!-- Header section /-->
    <!-- Hero Section -->
      <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.
            <h2 class="" style="text-align:center">
              Pixel perfect images can be found here.
            <div class="input-group">
                placeholder="search images"
                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>
              <div class="input-group-append">
                  class="btn btn-primary"
      <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]" />
      function images() {
        return {
          images: [],
          q: "",
          image_type: "",
          getImages: async function() {
            console.log("params", this.q, this.image_type);
            const response = await fetch(
            const data = await response.json();
            this.images = data.hits;

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.


{ box-sizing: border-box; } body { margin: 0; padding: 0; } h1, h2, h3, h4, h5, h6 { font-family: “Nunito”, sans-serif; } h1 { font-size: 66px; color: #fff; margin-bottom: 26px; } h2 { font-size: 30px; color: rgba(255, 255, 255, 0.8); letter-spacing: 0; line-height: 46px; margin-bottom: 54px; } p, a, span


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.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

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.

More great articles from LogRocket:

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 — .

Godwin Ekuma I learn so that I can solve problems.

2 Replies to “Getting started with Alpine”

  1. Nested x-for are only possible with a later version of AlpineJS than is used in this example.

Leave a Reply