Alpine is a relatively new JavaScript framework that borrows concepts from other frameworks such as Vue and React. Compared to these frameworks, Alpine is small and lightweight.
In this guide, we’ll zoom in on Alpine.js and Vue and demonstrate how to duplicate Vue tasks and features in Alpine. But this won’t be a typical X-framework-is-better-than-Y-framework post; both are unique in their own right and the more appropriate framework often depends on the use case.
Before we dive in, let’s take a moment to understand the philosophy behind Alpine. As its tagline states, Alpine is “a rugged, minimal framework for composing JavaScript behavior in your markup.” These behaviors are added as attributes using directives.
Alpine syntax is very much inspired by Vue. If you’re coming from a Vue background, you’ll most likely feel at home working with Alpine. Like Vue, Alpine uses a x-
prefix on all its directives. Unlike Vue, Alpine doesn’t use a virtual DOM, so we get to work with the actual DOM directly.
In Alpine, the x-data
directive is used to declare the scope and data of a component. It works as a combination of Vue’s el
and data
properties. It tells the framework to initialize a new component with the defined data object.
// in Vue <div id="app"></div> new Vue({ el: '#app', data: { posts: [] } // or in the case of a component data() { return { posts: [] } } }) // in Alpine <div x-data="{ posts: [] }"></div>
Instead of declaring the data inline, you can extract it into a reusable function.
// in Alpine <div x-data="posts()"></div> <script> function posts() { return { posts: [] } } </script>
Unlike Vue, Alpine doesn’t provide any explicit lifecycle hooks. However, you can achieve two of Vue’s lifecycle hooks — created
and mounted
— in Alpine using the x-init
directive.
The x-init
directive is used to execute some JavaScript when a component is initialized. This can range from simple expressions to complex functions. Think of it as created
hooks for Vue.
// in Vue <script> export default { name: 'App', data() { return { posts: [] }; }, created() { fetch("https://jsonplaceholder.typicode.com/posts") .then(response => response.json()) .then(data => (this.posts = data)) } } </script> // in Alpine <div x-data="{ posts: [] }" x-init=" fetch('https://jsonplaceholder.typicode.com/posts') .then(response => response.json()) .then(data => (posts = data)); " > <!-- `posts` will be array of a posts returned from the API --> </div>
To achieve Vue’s mounted
hook, you can return a callback from x-init
, which will be run after Alpine has made its initial updates to the DOM.
// in Vue <script> export default { name: 'App', data() { return { posts: [] }; }, mounted() { fetch("https://jsonplaceholder.typicode.com/posts") .then(response => response.json()) .then(data => (this.posts = data)) } } </script> // in Alpine <div x-data="{ posts: [] }" x-init="() => { fetch('https://jsonplaceholder.typicode.com/posts') .then(response => response.json()) .then(data => (posts = data)); } " ></div>
Unlike Vue, Alpine doesn’t use a mustache-like syntax for interpolation. Instead, it uses the x-text
and x-html
directives for interpolation, which work the same way as their Vue counterparts.
// in Vue <template> <div> <h3>{{ post.title }}</h3> <h3 v-text="post.title"></h3> <h3 v-html="post.title"></h3> </div> </template> <script> export default { name: 'App', data() { return { post: { title: "My first post" } } } } </script> // in Alpine <div x-data="{ post: { title: 'My first post' } }"> <h3 x-text="post.title"></h3> </div> <div x-data="{ post: { title: 'My first post' } }"> <h3 x-html="post.title"></h3> </div>
Just like Vue, Alpine makes handling mouse and keyboard events seamless by providing a x-on
directive. This directive attaches an event listener to an element and executes some JavaScript when the event is triggered.
// in Vue <template> <div> <button @click="deletePost(post)">Delete</button> </div> </template> <script> export default { name: 'App', data() { return { post: { title: "This is my first post" } }; }, methods: { deletePost(post) { confirm(`Want to delete: ${this.post.title}?`) } } }; </script> // in Alpine // inline <div x-data="{ post: { title: 'My first post' } }"> <button x-on:click="confirm(`Want to delete: ${post.title}?`)"> Delete </button> </div> // dedicated function <div x-data="{ post: { title: 'My first post' } }"> <button x-on:click="deletePost(post)"> Delete </button> </div> <script> function deletePost(post) { confirm(`Want to delete: ${post.title}?`) } </script>
If you’d rather use the shorthand form (@
), the above would be rewritten as:
// in Alpine <div x-data="{ post: { title: 'My first post' } }"> <button @click="confirm(`Want to delete: ${post.title}?`)"> Delete </button> </div>
Just like in Vue, you can access the DOM event using the $event
property.
Note: The $event
property is only available in DOM expressions. When using a function, you’ll have to pass as an argument to the function: <button @click="deletePost(post, $event)"></button>
.
Alpine also provides a handful of event modifiers, including keydown
, away
, prevent
, passive
, stop
, debounce
, self
, once
, window
, etc.
// in Alpine <a href="http://example.com" @click.prevent>Example.com</a>
When working with forms, Alpine provides two-way data binding, which keeps the value of an input in sync with the component data.
// in Vue <template> <div> <h3>{{ title }}</h3> <input type="text" v-model="title"> </div> </template> <script> export default { name: 'App', data() { return { title: '' } } } </script> // in Alpine <div x-data="{ title: '' }"> <h3 x-text="title"></h3> <input type="text" x-model="title" /> </div>
To iterate over an array, you can use the x-for
directive, which works like Vue’s v-for
.
// in Vue <ul> <li v-for="post in posts" :key="post.id"> {{ post.title }} </li> </ul> // in Alpine <ul> <template x-for="post in posts" :key="post.id"> <li x-text="post.title"></li> </template> </ul>
Unlike, however, v-for
, x-for
has some some caveats:
x-for
has to be on a template
tag instead of a regular DOM element.<template>
tag must have a single element root inside of it.Alpine supports attributes binding using the x-bind
directive. The x-bind
directive enables you to bind an HTML attribute to a JavaScript expression. The directive works the same way as v-bind
.
// in Vue <template> <div> <h3>{{ post.title }}</h3> <img v-bind:src="post.featuredImage" v-bind:alt="post.title" v-bind:class="{ hidden: isOnMobile }" > <button v-bind:disabled="post.isPublished">Publish</button> </div> </template> <script> export default { name: 'App', data() { return { post: { title: "My first post", featuredImage: "https://dummyimage.com/600x400", isPublished: false }, isOnMobile: true } } } </script> <style> .hidden { display: none } </style> // in Alpine // within the head tag <style> .hidden { display: none; } </style> <div x-data="{ post: { title: 'My first post', featuredImage: 'https://dummyimage.com/600x400', isPublished: false }, isOnMobile: true, }" > <h3 x-text="post.title"></h3> <img x-bind:src="post.featuredImage" x-bind:alt="post.title" x-bind:class="{ hidden: isOnMobile }" /> <button x-bind:disabled="post.isPublished">Publish</button> </div>
As with events handling, you can also use the shorthand form (:
).
// in Alpine <img :src="post.featuredImage" :alt="post.title" :class="{ hidden: isOnMobile }" /> <button :disabled="post.isPublished">Publish</button>
Alpine provides two directives for toggling visibilities — x-if
and x-show
— which work just like their Vue counterparts.
// in Vue <template> <div> <button @click="show = !show" v-text="show ? 'Close' : 'Open'"></button> <a href="#" v-if="isAdmin">Edit Post</a> </div> </template> <script> data() { return { show: false, isAdmin: false }; } </script> // in Alpine <div x-data="{ show: false, isAdmin: false }"> <button @click="show = !show" x-text="show ? 'Close' : 'Open'"></button> <template x-if="isAdmin"> <a href="#">Edit Post</a> </template> </div>
Like x-for
, x-if
has some caveats:
x-if
has to be on a template
tag instead of a regular DOM element.<template>
tag must have a single element root inside of it.Alpine also support watching for when a component’s property changes through a method called $watch()
.
// in Vue <template> <div> <p> <label>Dollar:</label> <input type="text" v-model="dollar"> </p> <p> <label>Naira:</label> <input type="text" v-model="naira"> </p> </div> </template> <script> export default { name: 'App', data() { return { dollar: 0, naira: 0 }; }, watch: { dollar(value) { this.naira = value * 390 }, naira(value) { this.dollar = value / 390 } } } </script> // in Alpine <div x-data="watcher()" x-init="initWatcher"> <p> <label>Dollar: </label> <input type="text" x-model="dollar" /> </p> <p> <label>Naira: </label> <input type="text" x-model="naira" /> </p> </div> <script> function watcher() { return { dollar: 0, naira: 0, initWatcher() { this.$watch("dollar", value => { this.naira = value * 390; }); this.$watch("naira", value => { this.dollar = value / 390; }); } }; } </script>
The $watch()
accepts two arguments: the property to watch and a callback that will be run whenever the property changes. The callback accepts the new value of the property.
As you can see, Alpine is similar to Vue in myriad ways. In some cases, in fact, you can take Vue component and simply swap the v-
prefixes with x-
to create an Alpine component.
To learn more about Alpine, check out the GitHub repo.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — 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 nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
One Reply to "Intro to Alpine.js for Vue developers"
Web components FTW… custom-web-component.net