99 KiB
Start Here
Create a blank HTML file somewhere on your computer with a name like: i-love-alpine.html
Using a text editor, fill the file with these contents:
<html>
<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
<h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>
</body>
</html>
Open your file in a web browser, if you see I ❤️ Alpine
, you're ready to rumble!
Now that you're all set up to play around, let's look at three practical examples as a foundation for teaching you the basics of Alpine. By the end of this exercise, you should be more than equipped to start building stuff on your own. Let's goooooo.
Building a counter
Let's start with a simple "counter" component to demonstrate the basics of state and event listening in Alpine, two core features.
Insert the following into the <body>
tag:
<div x-data="{ count: 0 }">
<button x-on:click="count++">Increment</button>
<span x-text="count"></span>
</div>
Now, you can see with 3 bits of Alpine sprinkled into this HTML, we've created an interactive "counter" component.
Let's walk through what's happening briefly:
Declaring data
<div x-data="{ count: 0 }">
Everything in Alpine starts with an x-data
directive. Inside of x-data
, in plain JavaScript, you declare an object of data that Alpine will track.
Every property inside this object will be made available to other directives inside this HTML element. In addition, when one of these properties changes, everything that relies on it will change as well.
Let's look at x-on
and see how it can access and modify the count
property from above:
Listening for events
<button x-on:click="count++">Increment</button>
x-on
is a directive you can use to listen for any event on an element. We're listening for a click
event in this case, so ours looks like x-on:click
.
You can listen for other events as you'd imagine. For example, listening for a mouseenter
event would look like this: x-on:mouseenter
.
When a click
event happens, Alpine will call the associated JavaScript expression, count++
in our case. As you can see, we have direct access to data declared in the x-data
expression.
You will often see
@
instead ofx-on:
. This is a shorter, friendlier syntax that many prefer. From now on, this documentation will likely use@
instead ofx-on:
.
Reacting to changes
<span x-text="count"></span>
x-text
is an Alpine directive you can use to set the text content of an element to the result of a JavaScript expression.
In this case, we're telling Alpine to always make sure that the contents of this span
tag reflect the value of the count
property.
In case it's not clear, x-text
, like most directives accepts a plain JavaScript expression as an argument. So for example, you could instead set its contents to: x-text="count * 2"
and the text content of the span
will now always be 2 times the value of count
.
Building a dropdown
Now that we've seen some basic functionality, let's keep going and look at an important directive in Alpine: x-show
, by building a contrived "dropdown" component.
Insert the following code into the <body>
tag:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div x-show="open" @click.outside="open = false">Contents...</div>
</div>
Toggle
If you load this component, you should see that the "Contents..." are hidden by default. You can toggle showing them on the page by clicking the "Toggle" button.
The x-data
and x-on
directives should be familiar to you from the previous example, so we'll skip those explanations.
Toggling elements
<div x-show="open" ...>Contents...</div>
x-show
is an extremely powerful directive in Alpine that can be used to show and hide a block of HTML on a page based on the result of a JavaScript expression, in our case: open
.
Listening for a click outside
<div ... @click.outside="open = false">Contents...</div>
You'll notice something new in this example: .outside
. Many directives in Alpine accept "modifiers" that are chained onto the end of the directive and are separated by periods.
In this case, .outside
tells Alpine to instead of listening for a click INSIDE the <div>
, to listen for the click only if it happens OUTSIDE the <div>
.
This is a convenience helper built into Alpine because this is a common need and implementing it by hand is annoying and complex.
Building a search input
Let's now build a more complex component and introduce a handful of other directives and patterns.
Insert the following code into the <body>
tag:
<div
x-data="{
search: '',
items: ['foo', 'bar', 'baz'],
get filteredItems() {
return this.items.filter(
i => i.startsWith(this.search)
)
}
}"
>
<input x-model="search" placeholder="Search...">
<ul>
<template x-for="item in filteredItems" :key="item">
<li x-text="item"></li>
</template>
</ul>
</div>
- foo
- bar
- baz
By default, all of the "items" (foo, bar, and baz) will be shown on the page, but you can filter them by typing into the text input. As you type, the list of items will change to reflect what you're searching for.
Now there's quite a bit happening here, so let's go through this snippet piece by piece.
Multi line formatting
The first thing I'd like to point out is that x-data
now has a lot more going on in it than before. To make it easier to write and read, we've split it up into multiple lines in our HTML. This is completely optional and we'll talk more in a bit about how to avoid this problem altogether, but for now, we'll keep all of this JavaScript directly in the HTML.
Binding to inputs
<input x-model="search" placeholder="Search...">
You'll notice a new directive we haven't seen yet: x-model
.
x-model
is used to "bind" the value of an input element with a data property: "search" from x-data="{ search: '', ... }"
in our case.
This means that anytime the value of the input changes, the value of "search" will change to reflect that.
x-model
is capable of much more than this simple example.
Computed properties using getters
The next bit I'd like to draw your attention to is the items
and filteredItems
properties from the x-data
directive.
{
items: ['foo', 'bar', 'baz'],
get filteredItems() {
return this.items.filter(
i => i.startsWith(this.search)
)
}
}
The items
property should be self-explanatory. Here we are setting the value of items
to a JavaScript array of 3 different items (foo, bar, and baz).
The interesting part of this snippet is the filteredItems
property.
Denoted by the get
prefix for this property, filteredItems
is a "getter" property in this object. This means we can access filteredItems
as if it was a normal property in our data object, but when we do, JavaScript will evaluate the provided function under the hood and return the result.
It's completely acceptable to forgo the get
and just make this a method that you can call from the template, but some prefer the nicer syntax of the getter.
Now let's look inside the filteredItems
getter and make sure we understand what's going on there:
return this.items.filter( i => i.startsWith(this.search))
This is all plain JavaScript. We are first getting the array of items (foo, bar, and baz) and filtering them using the provided callback: i => i.startsWith(this.search)
.
By passing in this callback to filter
, we are telling JavaScript to only return the items that start with the string: this.search
, which like we saw with x-model
will always reflect the value of the input.
You may notice that up until now, we haven't had to use this.
to reference properties. However, because we are working directly inside the x-data
object, we must reference any properties using this.[property]
instead of simply [property]
.
Because Alpine is a "reactive" framework. Any time the value of this.search
changes, parts of the template that use filteredItems
will automatically be updated.
Looping elements
Now that we understand the data part of our component, let's understand what's happening in the template that allows us to loop through filteredItems
on the page.
<ul>
<template x-for="item in filteredItems">
<li x-text="item"></li>
</template>
</ul>
The first thing to notice here is the x-for
directive. x-for
expressions take the following form: [item] in [items]
where [items]
is any array of data, and [item]
is the name of the variable that will be assigned to an iteration inside the loop.
Also notice that x-for
is declared on a <template>
element and not directly on the <li>
. This is a requirement of using x-for
. It allows Alpine to leverage the existing behavior of <template>
tags in the browser to its advantage.
Now any element inside the <template>
tag will be repeated for every item inside filteredItems
and all expressions evaluated inside the loop will have direct access to the iteration variable (item
in this case).
Recap
If you've made it this far, you've been exposed to the following directives in Alpine:
- x-data
- x-on
- x-text
- x-show
- x-model
- x-for
That's a great start, however, there are many more directives to sink your teeth into. The best way to absorb Alpine is to read through this documentation. No need to comb over every word, but if you at least glance through every page you will be MUCH more effective when using Alpine.
Happy Coding!
Essentials:
Installation
There are 2 ways to include Alpine into your project:
- Including it from a
<script>
tag - Importing it as a module
Either is perfectly valid. It all depends on the project's needs and the developer's taste.
From a script tag
This is by far the simplest way to get started with Alpine. Include the following <script>
tag in the head of your HTML page.
<html>
<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
</html>
Don't forget the "defer" attribute in the
<script>
tag.
Notice the @3.x.x
in the provided CDN link. This will pull the latest version of Alpine version 3. For stability in production, it's recommended that you hardcode the latest version in the CDN link.
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"></script>
That's it! Alpine is now available for use inside your page.
Note that you will still need to define a component with x-data
in order for any Alpine.js attributes to work. See https://github.com/alpinejs/alpine/discussions/3805 for more information.
As a module
If you prefer the more robust approach, you can install Alpine via NPM and import it into a bundle.
Run the following command to install it.
$ npm install alpinejs
Now import Alpine into your bundle and initialize it like so:
import Alpine from 'alpinejs' window.Alpine = Alpine Alpine.start()
The
window.Alpine = Alpine
bit is optional, but is nice to have for freedom and flexibility. Like when tinkering with Alpine from the devtools for example.
If you imported Alpine into a bundle, you have to make sure you are registering any extension code IN BETWEEN when you import the
Alpine
global object, and when you initialize Alpine by callingAlpine.start()
.
Ensure that
Alpine.start()
is only called once per page. Calling it more than once will result in multiple "instances" of Alpine running at the same time.
State
State (JavaScript data that Alpine watches for changes) is at the core of everything you do in Alpine. You can provide local data to a chunk of HTML, or make it globally available for use anywhere on a page using x-data
or Alpine.store()
respectively.
Local state
Alpine allows you to declare an HTML block's state in a single x-data
attribute without ever leaving your markup.
Here's a basic example:
<div x-data="{ open: false }"> ...</div>
Now any other Alpine syntax on or within this element will be able to access open
. And like you'd guess, when open
changes for any reason, everything that depends on it will react automatically.
Nesting data
Data is nestable in Alpine. For example, if you have two elements with Alpine data attached (one inside the other), you can access the parent's data from inside the child element.
<div x-data="{ open: false }"> <div x-data="{ label: 'Content:' }"> <span x-text="label"></span> <span x-show="open"></span> </div></div>
This is similar to scoping in JavaScript itself (code within a function can access variables declared outside that function.)
Like you may have guessed, if the child has a data property matching the name of a parent's property, the child property will take precedence.
Single-element data
Although this may seem obvious to some, it's worth mentioning that Alpine data can be used within the same element. For example:
<button x-data="{ label: 'Click Here' }" x-text="label"></button>
Data-less Alpine
Sometimes you may want to use Alpine functionality, but don't need any reactive data. In these cases, you can opt out of passing an expression to x-data
entirely. For example:
<button x-data @click="alert('I\'ve been clicked!')">Click Me</button>
Re-usable data
When using Alpine, you may find the need to re-use a chunk of data and/or its corresponding template.
If you are using a backend framework like Rails or Laravel, Alpine first recommends that you extract the entire block of HTML into a template partial or include.
If for some reason that isn't ideal for you or you're not in a back-end templating environment, Alpine allows you to globally register and re-use the data portion of a component using Alpine.data(...)
.
Alpine.data('dropdown', () => ({ open: false, toggle() { this.open = ! this.open }}))
Now that you've registered the "dropdown" data, you can use it inside your markup in as many places as you like:
<div x-data="dropdown"> <button @click="toggle">Expand</button> <span x-show="open">Content...</span></div> <div x-data="dropdown"> <button @click="toggle">Expand</button> <span x-show="open">Some Other Content...</span></div>
Global state
If you wish to make some data available to every component on the page, you can do so using Alpine's "global store" feature.
You can register a store using Alpine.store(...)
, and reference one with the magic $store()
method.
Let's look at a simple example. First we'll register the store globally:
Alpine.store('tabs', { current: 'first', items: ['first', 'second', 'third'],})
Now we can access or modify its data from anywhere on our page:
<div x-data> <template x-for="tab in $store.tabs.items"> ... </template></div> <div x-data> <button @click="$store.tabs.current = 'first'">First Tab</button> <button @click="$store.tabs.current = 'second'">Second Tab</button> <button @click="$store.tabs.current = 'third'">Third Tab</button></div>
Templating
Alpine offers a handful of useful directives for manipulating the DOM on a web page.
Let's cover a few of the basic templating directives here, but be sure to look through the available directives in the sidebar for an exhaustive list.
Text content
Alpine makes it easy to control the text content of an element with the x-text
directive.
<div x-data="{ title: 'Start Here' }"> <h1 x-text="title"></h1></div>
Now, Alpine will set the text content of the <h1>
with the value of title
("Start Here"). When title
changes, so will the contents of <h1>
.
Like all directives in Alpine, you can use any JavaScript expression you like. For example:
<span x-text="1 + 2"></span>
The <span>
will now contain the sum of "1" and "2".
Toggling elements
Toggling elements is a common need in web pages and applications. Dropdowns, modals, dialogues, "show-more"s, etc... are all good examples.
Alpine offers the x-show
and x-if
directives for toggling elements on a page.
x-show
Here's a simple toggle component using x-show
.
<div x-data="{ open: false }"> <button @click="open = ! open">Expand</button> <div x-show="open"> Content... </div></div>
Now the entire <div>
containing the contents will be shown and hidden based on the value of open
.
Under the hood, Alpine adds the CSS property display: none;
to the element when it should be hidden.
This works well for most cases, but sometimes you may want to completely add and remove the element from the DOM entirely. This is what x-if
is for.
x-if
Here is the same toggle from before, but this time using x-if
instead of x-show
.
<div x-data="{ open: false }"> <button @click="open = ! open">Expand</button> <template x-if="open"> <div> Content... </div> </template></div>
Notice that x-if
must be declared on a <template>
tag. This is so that Alpine can leverage the existing browser behavior of the <template>
element and use it as the source of the target <div>
to be added and removed from the page.
When open
is true, Alpine will append the <div>
to the <template>
tag, and remove it when open
is false.
Toggling with transitions
Alpine makes it simple to smoothly transition between "shown" and "hidden" states using the x-transition
directive.
x-transition
only works withx-show
, not withx-if
.
Here is, again, the simple toggle example, but this time with transitions applied:
<div x-data="{ open: false }"> <button @click="open = ! open">Expands</button> <div x-show="open" x-transition> Content... </div></div>
Let's zoom in on the portion of the template dealing with transitions:
<div x-show="open" x-transition>
x-transition
by itself will apply sensible default transitions (fade and scale) to the toggle.
There are two ways to customize these transitions:
- Transition helpers
- Transition CSS classes.
Let's take a look at each of these approaches:
Transition helpers
Let's say you wanted to make the duration of the transition longer, you can manually specify that using the .duration
modifier like so:
<div x-show="open" x-transition.duration.500ms>
Now the transition will last 500 milliseconds.
If you want to specify different values for in and out transitions, you can use x-transition:enter
and x-transition:leave
:
<div x-show="open" x-transition:enter.duration.500ms x-transition:leave.duration.1000ms>
Additionally, you can add either .opacity
or .scale
to only transition that property. For example:
<div x-show="open" x-transition.opacity>
Transition classes
If you need more fine-grained control over the transitions in your application, you can apply specific CSS classes at specific phases of the transition using the following syntax (this example uses Tailwind CSS):
<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform scale-90" x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-end="opacity-0 transform scale-90">...</div>
Binding attributes
You can add HTML attributes like class
, style
, disabled
, etc... to elements in Alpine using the x-bind
directive.
Here is an example of a dynamically bound class
attribute:
<button x-data="{ red: false }" x-bind:class="red ? 'bg-red' : ''" @click="red = ! red"> Toggle Red</button>
As a shortcut, you can leave out the x-bind
and use the shorthand :
syntax directly:
<button ... :class="red ? 'bg-red' : ''">
Toggling classes on and off based on data inside Alpine is a common need. Here's an example of toggling a class using Alpine's class
binding object syntax: (Note: this syntax is only available for class
attributes)
<div x-data="{ open: true }"> <span :class="{ 'hidden': ! open }">...</span></div>
Now the hidden
class will be added to the element if open
is false, and removed if open
is true.
Looping elements
Alpine allows for iterating parts of your template based on JavaScript data using the x-for
directive. Here is a simple example:
<div x-data="{ statuses: ['open', 'closed', 'archived'] }"> <template x-for="status in statuses"> <div x-text="status"></div> </template></div>
Similar to x-if
, x-for
must be applied to a <template>
tag. Internally, Alpine will append the contents of <template>
tag for every iteration in the loop.
As you can see the new status
variable is available in the scope of the iterated templates.
Inner HTML
Alpine makes it easy to control the HTML content of an element with the x-html
directive.
<div x-data="{ title: '<h1>Start Here</h1>' }"> <div x-html="title"></div></div>
Now, Alpine will set the text content of the <div>
with the element <h1>Start Here</h1>
. When title
changes, so will the contents of <h1>
.
⚠️ Only use on trusted content and never on user-provided content. ⚠️ Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.
Events
Alpine makes it simple to listen for browser events and react to them.
Listening for simple events
By using x-on
, you can listen for browser events that are dispatched on or within an element.
Here's a basic example of listening for a click on a button:
<button x-on:click="console.log('clicked')">...</button>
As an alternative, you can use the event shorthand syntax if you prefer: @
. Here's the same example as before, but using the shorthand syntax (which we'll be using from now on):
<button @click="...">...</button>
In addition to click
, you can listen for any browser event by name. For example: @mouseenter
, @keyup
, etc... are all valid syntax.
Listening for specific keys
Let's say you wanted to listen for the enter
key to be pressed inside an <input>
element. Alpine makes this easy by adding the .enter
like so:
<input @keyup.enter="...">
You can even combine key modifiers to listen for key combinations like pressing enter
while holding shift
:
<input @keyup.shift.enter="...">
Preventing default
When reacting to browser events, it is often necessary to "prevent default" (prevent the default behavior of the browser event).
For example, if you want to listen for a form submission but prevent the browser from submitting a form request, you can use .prevent
:
<form @submit.prevent="...">...</form>
You can also apply .stop
to achieve the equivalent of event.stopPropagation()
.
Accessing the event object
Sometimes you may want to access the native browser event object inside your own code. To make this easy, Alpine automatically injects an $event
magic variable:
<button @click="$event.target.remove()">Remove Me</button>
Dispatching custom events
In addition to listening for browser events, you can dispatch them as well. This is extremely useful for communicating with other Alpine components or triggering events in tools outside of Alpine itself.
Alpine exposes a magic helper called $dispatch
for this:
<div @foo="console.log('foo was dispatched')">
<button @click="$dispatch('foo')"></button>
</div>
As you can see, when the button is clicked, Alpine will dispatch a browser event called "foo", and our @foo
listener on the <div>
will pick it up and react to it.
Listening for events on window
Because of the nature of events in the browser, it is sometimes useful to listen to events on the top-level window object.
This allows you to communicate across components completely like the following example:
<div x-data>
<button @click="$dispatch('foo')"></button>
</div>
<div x-data @foo.window="console.log('foo was dispatched')">...</div>
In the above example, if we click the button in the first component, Alpine will dispatch the "foo" event. Because of the way events work in the browser, they "bubble" up through parent elements all the way to the top-level "window".
Now, because in our second component we are listening for "foo" on the window (with .window
), when the button is clicked, this listener will pick it up and log the "foo was dispatched" message.
Lifecycle
Alpine has a handful of different techniques for hooking into different parts of its lifecycle. Let's go through the most useful ones to familiarize yourself with:
Element initialization
Another extremely useful lifecycle hook in Alpine is the x-init
directive.
x-init
can be added to any element on a page and will execute any JavaScript you call inside it when Alpine begins initializing that element.
<button x-init="console.log('Im initing')">
In addition to the directive, Alpine will automatically call any init()
methods stored on a data object. For example:
Alpine.data('dropdown', () => ({
init() {
// I get called before the element using this data initializes.
}
}))
After a state change
Alpine allows you to execute code when a piece of data (state) changes. It offers two different APIs for such a task: $watch
and x-effect
.
$watch
<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
As you can see above, $watch
allows you to hook into data changes using a dot-notation key. When that piece of data changes, Alpine will call the passed callback and pass it the new value. along with the old value before the change.
x-effect
x-effect
uses the same mechanism under the hood as $watch
but has very different usage.
Instead of specifying which data key you wish to watch, x-effect
will call the provided code and intelligently look for any Alpine data used within it. Now when one of those pieces of data changes, the x-effect
expression will be re-run.
Here's the same bit of code from the $watch
example rewritten using x-effect
:
<div x-data="{ open: false }" x-effect="console.log(open)">
Now, this expression will be called right away, and re-called every time open
is updated.
The two main behavioral differences with this approach are:
- The provided code will be run right away AND when data changes (
$watch
is "lazy" -- won't run until the first data change) - No knowledge of the previous value. (The callback provided to
$watch
receives both the new value AND the old one)
Alpine initialization
alpine:init
Ensuring a bit of code executes after Alpine is loaded, but BEFORE it initializes itself on the page is a necessary task.
This hook allows you to register custom data, directives, magics, etc. before Alpine does its thing on a page.
You can hook into this point in the lifecycle by listening for an event that Alpine dispatches called: alpine:init
document.addEventListener('alpine:init', () => {
Alpine.data(...)
})
alpine:initialized
Alpine also offers a hook that you can use to execute code AFTER it's done initializing called alpine:initialized
:
document.addEventListener('alpine:initialized', () => {
//
})
Directives
x-data
Everything in Alpine starts with the x-data
directive.
x-data
defines a chunk of HTML as an Alpine component and provides the reactive data for that component to reference.
Here's an example of a contrived dropdown component:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle Content</button>
<div x-show="open">Content...</div>
</div>
Don't worry about the other directives in this example (@click
and x-show
), we'll get to those in a bit. For now, let's focus on x-data
.
Scope
Properties defined in an x-data
directive are available to all element children. Even ones inside other, nested x-data
components.
For example:
<div x-data="{ foo: 'bar' }">
<span x-text="foo"><!-- Will output: "bar" --></span>
<div x-data="{ bar: 'baz' }">
<span x-text="foo"><!-- Will output: "bar" --></span>
<div x-data="{ foo: 'bob' }">
<span x-text="foo"><!-- Will output: "bob" --></span>
</div>
</div>
</div>
Methods
Because x-data
is evaluated as a normal JavaScript object, in addition to state, you can store methods and even getters.
For example, let's extract the "Toggle Content" behavior into a method on x-data
.
<div x-data="{ open: false, toggle() { this.open = ! this.open } }">
<button @click="toggle()">Toggle Content</button>
<div x-show="open">Content...</div>
</div>
Notice the added toggle() { this.open = ! this.open }
method on x-data
. This method can now be called from anywhere inside the component.
You'll also notice the usage of this.
to access state on the object itself. This is because Alpine evaluates this data object like any standard JavaScript object with a this
context.
If you prefer, you can leave the calling parenthesis off of the toggle
method completely. For example:
<!-- Before -->
<button @click="toggle()">...</button>
<!-- After -->
<button @click="toggle">...</button>
Getters
JavaScript getters are handy when the sole purpose of a method is to return data based on other state.
Think of them like "computed properties" (although, they are not cached like Vue's computed properties).
Let's refactor our component to use a getter called isOpen
instead of accessing open
directly.
<div x-data="{
open: false,
get isOpen() { return this.open },
toggle() { this.open = ! this.open },}">
<button @click="toggle()">Toggle Content</button>
<div x-show="isOpen">Content...</div>
</div>
Notice the "Content" now depends on the isOpen
getter instead of the open
property directly.
In this case there is no tangible benefit. But in some cases, getters are helpful for providing a more expressive syntax in your components.
Data-less components
Occasionally, you want to create an Alpine component, but you don't need any data.
In these cases, you can always pass in an empty object.
<div x-data="{}">
However, if you wish, you can also eliminate the attribute value entirely if it looks better to you.
<div x-data>
Single-element components
Sometimes you may only have a single element inside your Alpine component, like the following:
<div x-data="{ open: true }">
<button @click="open = false" x-show="open">Hide Me</button>
</div>
In these cases, you can declare x-data
directly on that single element:
<button x-data="{ open: true }" @click="open = false" x-show="open">Hide Me</button>
Re-usable Data
If you find yourself duplicating the contents of x-data
, or you find the inline syntax verbose, you can extract the x-data
object out to a dedicated component using Alpine.data
.
Here's a quick example:
<div x-data="dropdown">
<button @click="toggle">Toggle Content</button>
<div x-show="open">Content...</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
toggle() {
this.open = ! this.open
},
}))
})
</script>
x-init
The x-init
directive allows you to hook into the initialization phase of any element in Alpine.
<div x-init="console.log('I\'m being initialized!')"></div>
In the above example, "I'm being initialized!" will be output in the console before it makes further DOM updates.
Consider another example where x-init
is used to fetch some JSON and store it in x-data
before the component is processed.
<div x-data="{ posts: [] }"
x-init="posts = await (await fetch('/posts')).json()">...</div>
$nextTick
Sometimes, you want to wait until after Alpine has completely finished rendering to execute some code.
This would be something like useEffect(..., [])
in react, or mount
in Vue.
By using Alpine's internal $nextTick
magic, you can make this happen.
<div x-init="$nextTick(() => { ... })"></div>
Standalone x-init
You can add x-init
to any elements inside or outside an x-data
HTML block. For example:
<div x-data>
<span x-init="console.log('I can initialize')"></span>
</div>
<span x-init="console.log('I can initialize too')"></span>
Auto-evaluate init() method
If the x-data
object of a component contains an init()
method, it will be called automatically. For example:
<div x-data="{
init() {
console.log('I am called automatically')
}}">...</div>
This is also the case for components that were registered using the Alpine.data()
syntax.
Alpine.data('dropdown', () => ({
init() {
console.log('I will get evaluated when initializing each "dropdown" component.')
},
}))
If you have both an x-data
object containing an init()
method and an x-init
directive, the x-data
method will be called before the directive.
<div
x-data="{
init() {
console.log('I am called first')
}
}"
x-init="console.log('I am called second')">...
</div>
x-show
x-show
is one of the most useful and powerful directives in Alpine. It provides an expressive way to show and hide DOM elements.
Here's an example of a simple dropdown component using x-show
.
<div x-data="{ open: false }">
<button x-on:click="open = ! open">Toggle Dropdown</button>
<div x-show="open">Dropdown Contents...</div>
</div>
When the "Toggle Dropdown" button is clicked, the dropdown will show and hide accordingly.
If the "default" state of an
x-show
on page load is "false", you may want to usex-cloak
on the page to avoid "page flicker" (The effect that happens when the browser renders your content before Alpine is finished initializing and hiding it.) You can learn more aboutx-cloak
in its documentation.
With transitions
If you want to apply smooth transitions to the x-show
behavior, you can use it in conjunction with x-transition
. You can learn more about that directive here, but here's a quick example of the same component as above, just with transitions applied.
<div x-data="{ open: false }">
<button x-on:click="open = ! open">Toggle Dropdown</button>
<div x-show="open" x-transition>Dropdown Contents...</div>
</div>
Using the important modifier
Sometimes you need to apply a little more force to actually hide an element. In cases where a CSS selector applies the display
property with the !important
flag, it will take precedence over the inline style set by Alpine.
In these cases you may use the .important
modifier to set the inline style to display: none !important
.
<div x-data="{ open: false }">
<button x-on:click="open = ! open">Toggle Dropdown</button>
<div x-show.important="open">Dropdown Contents...</div>
</div>
x-bind
x-bind
allows you to set HTML attributes on elements based on the result of JavaScript expressions.
For example, here's a component where we will use x-bind
to set the placeholder value of an input.
<div x-data="{ placeholder: 'Type here...' }">
<input type="text" x-bind:placeholder="placeholder">
</div>
Shorthand syntax
If x-bind:
is too verbose for your liking, you can use the shorthand: :
. For example, here is the same input element as above, but refactored to use the shorthand syntax.
<input type="text" :placeholder="placeholder">
Binding classes
x-bind
is most often useful for setting specific classes on an element based on your Alpine state.
Here's a simple example of a simple dropdown toggle, but instead of using x-show
, we'll use a "hidden" class to toggle an element.
<div x-data="{ open: false }">
<button x-on:click="open = ! open">Toggle Dropdown</button>
<div :class="open ? '' : 'hidden'">Dropdown Contents...</div>
</div>
Now, when open
is false
, the "hidden" class will be added to the dropdown.
Shorthand conditionals
In cases like these, if you prefer a less verbose syntax you can use JavaScript's short-circuit evaluation instead of standard conditionals:
<div :class="show ? '' : 'hidden'">
<!-- Is equivalent to: -->
<div :class="show || 'hidden'">
The inverse is also available to you. Suppose instead of open
, we use a variable with the opposite value: closed
.
<div :class="closed ? 'hidden' : ''">
<!-- Is equivalent to: -->
<div :class="closed && 'hidden'">
Class object syntax
Alpine offers an additional syntax for toggling classes if you prefer. By passing a JavaScript object where the classes are the keys and booleans are the values, Alpine will know which classes to apply and which to remove. For example:
<div :class="{ 'hidden': ! show }">
This technique offers a unique advantage to other methods. When using object-syntax, Alpine will NOT preserve original classes applied to an element's class
attribute.
For example, if you wanted to apply the "hidden" class to an element before Alpine loads, AND use Alpine to toggle its existence you can only achieve that behavior using object-syntax:
<div class="hidden" :class="{ 'hidden': ! show }">
In case that confused you, let's dig deeper into how Alpine handles x-bind:class
differently than other attributes.
Special behavior
x-bind:class
behaves differently than other attributes under the hood.
Consider the following case.
<div class="opacity-50" :class="hide && 'hidden'">
If "class" were any other attribute, the :class
binding would overwrite any existing class attribute, causing opacity-50
to be overwritten by either hidden
or ''
.
However, Alpine treats class
bindings differently. It's smart enough to preserve existing classes on an element.
For example, if hide
is true, the above example will result in the following DOM element:
<div class="opacity-50 hidden">
If hide
is false, the DOM element will look like:
<div class="opacity-50">
This behavior should be invisible and intuitive to most users, but it is worth mentioning explicitly for the inquiring developer or any special cases that might crop up.
Binding styles
Similar to the special syntax for binding classes with JavaScript objects, Alpine also offers an object-based syntax for binding style
attributes.
Just like the class objects, this syntax is entirely optional. Only use it if it affords you some advantage.
<div :style="{ color: 'red', display: 'flex' }">
<!-- Will render: -->
<div style="color: red; display: flex;" ...>
Conditional inline styling is possible using expressions just like with x-bind:class. Short circuit operators can be used here as well by using a styles object as the second operand.
<div x-bind:style="true && { color: 'red' }">
<!-- Will render: -->
<div style="color: red;">
One advantage of this approach is being able to mix it in with existing styles on an element:
<div style="padding: 1rem;" :style="{ color: 'red', display: 'flex' }">
<!-- Will render: -->
<div style="padding: 1rem; color: red; display: flex;" ...>
And like most expressions in Alpine, you can always use the result of a JavaScript expression as the reference:
<div x-data="{ styles: { color: 'red', display: 'flex' }}">
<div :style="styles"></div>
<!-- Will render: -->
<div ...>
<div style="color: red; display: flex;" ...></div>
Binding Alpine Directives Directly
x-bind
allows you to bind an object of different directives and attributes to an element.
The object keys can be anything you would normally write as an attribute name in Alpine. This includes Alpine directives and modifiers, but also plain HTML attributes. The object values are either plain strings, or in the case of dynamic Alpine directives, callbacks to be evaluated by Alpine.
<div x-data="dropdown()">
<button x-bind="trigger">Open Dropdown</button>
<span x-bind="dialogue">Dropdown Contents</span>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
trigger: {
['x-ref']: 'trigger',
['@click']() {
this.open = true
},
},
dialogue: {
['x-show']() {
return this.open
},
['@click.outside']() {
this.open = false
},
},
}))
})
</script>
There are a couple of caveats to this usage of x-bind
:
When the directive being "bound" or "applied" is
x-for
, you should return a normal expression string from the callback. For example:['x-for']() { return 'item in items' }
x-on
x-on
allows you to easily run code on dispatched DOM events.
Here's an example of simple button that shows an alert when clicked.
<button x-on:click="alert('Hello World!')">Say Hi</button>
x-on
can only listen for events with lower case names, as HTML attributes are case-insensitive. Writingx-on:CLICK
will listen for an event namedclick
. If you need to listen for a custom event with a camelCase name, you can use the.camel
helper to work around this limitation. Alternatively, you can usex-bind
to attach anx-on
directive to an element in javascript code (where case will be preserved).
Shorthand syntax
If x-on:
is too verbose for your tastes, you can use the shorthand syntax: @
.
Here's the same component as above, but using the shorthand syntax instead:
<button @click="alert('Hello World!')">Say Hi</button>
The event object
If you wish to access the native JavaScript event object from your expression, you can use Alpine's magic $event
property.
<button @click="alert($event.target.getAttribute('message'))" message="Hello World">Say Hi</button>
In addition, Alpine also passes the event object to any methods referenced without trailing parenthesis. For example:
<button @click="handleClick">...</button>
<script>
function handleClick(e) {
// Now you can access the event object (e) directly
}
</script>
Keyboard events
Alpine makes it easy to listen for keydown
and keyup
events on specific keys.
Here's an example of listening for the Enter
key inside an input element.
<input type="text" @keyup.enter="alert('Submitted!')">
You can also chain these key modifiers to achieve more complex listeners.
Here's a listener that runs when the Shift
key is held and Enter
is pressed, but not when Enter
is pressed alone.
<input type="text" @keyup.shift.enter="alert('Submitted!')">
You can directly use any valid key names exposed via KeyboardEvent.key
as modifiers by converting them to kebab-case.
<input type="text" @keyup.page-down="alert('Submitted!')">
For easy reference, here is a list of common keys you may want to listen for.
Modifier | Keyboard Key |
---|---|
.shift |
Shift |
.enter |
Enter |
.space |
Space |
.ctrl |
Ctrl |
.cmd |
Cmd |
.meta |
Cmd on Mac, Windows key on Windows |
.alt |
Alt |
.up .down .left .right |
Up/Down/Left/Right arrows |
.escape |
Escape |
.tab |
Tab |
.caps-lock |
Caps Lock |
.equal |
Equal, = |
.period |
Period, . |
.comma |
Comma, , |
.slash |
Forward Slash, / |
Mouse events
Like the above Keyboard Events, Alpine allows the use of some key modifiers for handling click
events.
Modifier | Event Key |
---|---|
.shift |
shiftKey |
.ctrl |
ctrlKey |
.cmd |
metaKey |
.meta |
metaKey |
.alt |
altKey |
These work on click
, auxclick
, context
and dblclick
events, and even mouseover
, mousemove
, mouseenter
, mouseleave
, mouseout
, mouseup
and mousedown
.
Here's an example of a button that changes behaviour when the Shift
key is held down.
<button type="button"
@click="message = 'selected'"
@click.shift="message = 'added to selection'">
@mousemove.shift="message = 'add to selection'"
@mouseout="message = 'select'"
x-text="message"></button>
Note: Normal click events with some modifiers (like
ctrl
) will automatically becomecontextmenu
events in most browsers. Similarly,right-click
events will trigger acontextmenu
event, but will also trigger anauxclick
event if thecontextmenu
event is prevented.
Custom events
Alpine event listeners are a wrapper for native DOM event listeners. Therefore, they can listen for ANY DOM event, including custom events.
Here's an example of a component that dispatches a custom DOM event and listens for it as well.
<div x-data @foo="alert('Button Was Clicked!')">
<button @click="$event.target.dispatchEvent(new CustomEvent('foo', { bubbles: true }))">...</button>
</div>
When the button is clicked, the @foo
listener will be called.
Because the .dispatchEvent
API is verbose, Alpine offers a $dispatch
helper to simplify things.
Here's the same component re-written with the $dispatch
magic property.
<div x-data @foo="alert('Button Was Clicked!')">
<button @click="$dispatch('foo')">...</button>
</div>
Modifiers
Alpine offers a number of directive modifiers to customize the behavior of your event listeners.
.prevent
.prevent
is the equivalent of calling .preventDefault()
inside a listener on the browser event object.
<form @submit.prevent="console.log('submitted')" action="/foo">
<button>Submit</button>
</form>
In the above example, with the .prevent
, clicking the button will NOT submit the form to the /foo
endpoint. Instead, Alpine's listener will handle it and "prevent" the event from being handled any further.
.stop
Similar to .prevent
, .stop
is the equivalent of calling .stopPropagation()
inside a listener on the browser event object.
<div @click="console.log('I will not get logged')">
<button @click.stop>Click Me</button>
</div>
In the above example, clicking the button WON'T log the message. This is because we are stopping the propagation of the event immediately and not allowing it to "bubble" up to the <div>
with the @click
listener on it.
.outside
.outside
is a convenience helper for listening for a click outside of the element it is attached to. Here's a simple dropdown component example to demonstrate:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div x-show="open" @click.outside="open = false">Contents...</div>
</div>
In the above example, after showing the dropdown contents by clicking the "Toggle" button, you can close the dropdown by clicking anywhere on the page outside the content.
This is because .outside
is listening for clicks that DON'T originate from the element it's registered on.
It's worth noting that the
.outside
expression will only be evaluated when the element it's registered on is visible on the page. Otherwise, there would be nasty race conditions where clicking the "Toggle" button would also fire the@click.outside
handler when it is not visible.
.window
When the .window
modifier is present, Alpine will register the event listener on the root window
object on the page instead of the element itself.
<div @keyup.escape.window="...">...</div>
The above snippet will listen for the "escape" key to be pressed ANYWHERE on the page.
Adding .window
to listeners is extremely useful for these sorts of cases where a small part of your markup is concerned with events that take place on the entire page.
.document
.document
works similarly to .window
only it registers listeners on the document
global, instead of the window
global.
.once
By adding .once
to a listener, you are ensuring that the handler is only called ONCE.
<button @click.once="console.log('I will only log once')">...</button>
.debounce
Sometimes it is useful to "debounce" an event handler so that it only is called after a certain period of inactivity (250 milliseconds by default).
For example if you have a search field that fires network requests as the user types into it, adding a debounce will prevent the network requests from firing on every single keystroke.
<input @input.debounce="fetchResults">
Now, instead of calling fetchResults
after every keystroke, fetchResults
will only be called after 250 milliseconds of no keystrokes.
If you wish to lengthen or shorten the debounce time, you can do so by trailing a duration after the .debounce
modifier like so:
<input @input.debounce.500ms="fetchResults">
Now, fetchResults
will only be called after 500 milliseconds of inactivity.
.throttle
.throttle
is similar to .debounce
except it will release a handler call every 250 milliseconds instead of deferring it indefinitely.
This is useful for cases where there may be repeated and prolonged event firing and using .debounce
won't work because you want to still handle the event every so often.
For example:
<div @scroll.window.throttle="handleScroll">...</div>
The above example is a great use case of throttling. Without .throttle
, the handleScroll
method would be fired hundreds of times as the user scrolls down a page. This can really slow down a site. By adding .throttle
, we are ensuring that handleScroll
only gets called every 250 milliseconds.
Fun Fact: This exact strategy is used on this very documentation site to update the currently highlighted section in the right sidebar.
Just like with .debounce
, you can add a custom duration to your throttled event:
<div @scroll.window.throttle.750ms="handleScroll">...</div>
Now, handleScroll
will only be called every 750 milliseconds.
.self
By adding .self
to an event listener, you are ensuring that the event originated on the element it is declared on, and not from a child element.
<button @click.self="handleClick">Click Me<img src="..."></button>
In the above example, we have an <img>
tag inside the <button>
tag. Normally, any click originating within the <button>
element (like on <img>
for example), would be picked up by a @click
listener on the button.
However, in this case, because we've added a .self
, only clicking the button itself will call handleClick
. Only clicks originating on the <img>
element will not be handled.
.camel
<div @custom-event.camel="handleCustomEvent">...</div>
Sometimes you may want to listen for camelCased events such as customEvent
in our example. Because camelCasing inside HTML attributes is not supported, adding the .camel
modifier is necessary for Alpine to camelCase the event name internally.
By adding .camel
in the above example, Alpine is now listening for customEvent
instead of custom-event
.
.dot
<div @custom-event.dot="handleCustomEvent">...</div>
Similar to the .camelCase
modifier there may be situations where you want to listen for events that have dots in their name (like custom.event
). Since dots within the event name are reserved by Alpine you need to write them with dashes and add the .dot
modifier.
In the code example above custom-event.dot
will correspond to the event name custom.event
.
.passive
Browsers optimize scrolling on pages to be fast and smooth even when JavaScript is being executed on the page. However, improperly implemented touch and wheel listeners can block this optimization and cause poor site performance.
If you are listening for touch events, it's important to add .passive
to your listeners to not block scroll performance.
<div @touchstart.passive="...">...</div>
.capture
Add this modifier if you want to execute this listener in the event's capturing phase, e.g. before the event bubbles from the target element up the DOM.
<div @click.capture="console.log('I will log first')">
<button @click="console.log('I will log second')"></button>
</div>
x-text
x-text
sets the text content of an element to the result of a given expression.
Here's a basic example of using x-text
to display a user's username.
<div x-data="{ username: 'calebporzio' }">
Username: <strong x-text="username"></strong>
</div>
Now the <strong>
tag's inner text content will be set to "calebporzio".
x-html
x-html
sets the "innerHTML" property of an element to the result of a given expression.
⚠️ Only use on trusted content and never on user-provided content. ⚠️ Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.
Here's a basic example of using x-html
to display a user's username.
<div x-data="{ username: '<strong>calebporzio</strong>' }">
Username: <span x-html="username"></span>
</div>
Now the <span>
tag's inner HTML will be set to "calebporzio".
x-model
x-model
allows you to bind the value of an input element to Alpine data.
Here's a simple example of using x-model
to bind the value of a text field to a piece of data in Alpine.
<div x-data="{ message: '' }">
<input type="text" x-model="message">
<span x-text="message"></span>
</div>
Now as the user types into the text field, the message
will be reflected in the <span>
tag.
x-model
is two-way bound, meaning it both "sets" and "gets". In addition to changing data, if the data itself changes, the element will reflect the change.
We can use the same example as above but this time, we'll add a button to change the value of the message
property.
<div x-data="{ message: '' }">
<input type="text" x-model="message">
<button x-on:click="message = 'changed'">Change Message</button>
</div>
Now when the <button>
is clicked, the input element's value will instantly be updated to "changed".
x-model
works with the following input elements:
<input type="text">
<textarea>
<input type="checkbox">
<input type="radio">
<select>
<input type="range">
Text inputs
<input type="text" x-model="message">
<span x-text="message"></span>
Textarea inputs
<textarea x-model="message"></textarea>
<span x-text="message"></span>
Checkbox inputs
Single checkbox with boolean
<input type="checkbox" id="checkbox" x-model="show">
<label for="checkbox" x-text="show"></label>
Multiple checkboxes bound to array
<input type="checkbox" value="red" x-model="colors">
<input type="checkbox" value="orange" x-model="colors">
<input type="checkbox" value="yellow" x-model="colors"> Colors: <span x-text="colors"></span>
Radio inputs
<input type="radio" value="yes" x-model="answer">
<input type="radio" value="no" x-model="answer">
Answer: <span x-text="answer"></span>
Select inputs
Single select
<select x-model="color">
<option>Red</option>
<option>Orange</option>
<option>Yellow</option>
</select>
Color: <span x-text="color"></span>
Single select with placeholder
<select x-model="color">
<option value="" disabled>Select A Color</option>
<option>Red</option>
<option>Orange</option> <option>Yellow</option></select> Color: <span x-text="color"></span>
Multiple select
<select x-model="color" multiple>
<option>Red</option>
<option>Orange</option>
<option>Yellow</option>
</select>
Colors: <span x-text="color"></span>
Dynamically populated Select Options
<select x-model="color">
<template x-for="color in ['Red', 'Orange', 'Yellow']">
<option x-text="color"></option>
</template>
</select>
Color: <span x-text="color"></span>
Range inputs
<input type="range" x-model="range" min="0" max="1" step="0.1">
<span x-text="range"></span>
Modifiers
.lazy
On text inputs, by default, x-model
updates the property on every keystroke. By adding the .lazy
modifier, you can force an x-model
input to only update the property when user focuses away from the input element.
This is handy for things like real-time form-validation where you might not want to show an input validation error until the user "tabs" away from a field.
<input type="text" x-model.lazy="username">
<span x-show="username.length > 20">The username is too long.</span>
.number
By default, any data stored in a property via x-model
is stored as a string. To force Alpine to store the value as a JavaScript number, add the .number
modifier.
<input type="text" x-model.number="age"><span x-text="typeof age"></span>
.boolean
By default, any data stored in a property via x-model
is stored as a string. To force Alpine to store the value as a JavaScript boolean, add the .boolean
modifier. Both integers (1/0) and strings (true/false) are valid boolean values.
<select x-model.boolean="isActive">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
<span x-text="typeof isActive"></span>
.debounce
By adding .debounce
to x-model
, you can easily debounce the updating of bound input.
This is useful for things like real-time search inputs that fetch new data from the server every time the search property changes.
<input type="text" x-model.debounce="search">
The default debounce time is 250 milliseconds, you can easily customize this by adding a time modifier like so.
<input type="text" x-model.debounce.500ms="search">
.throttle
Similar to .debounce
you can limit the property update triggered by x-model
to only updating on a specified interval.
The default throttle interval is 250 milliseconds, you can easily customize this by adding a time modifier like so.
<input type="text" x-model.throttle.500ms="search">
.fill
By default, if an input has a value attribute, it is ignored by Alpine and instead, the value of the input is set to the value of the property bound using x-model
.
But if a bound property is empty, then you can use an input's value attribute to populate the property by adding the .fill
modifier.
Programmatic access
Alpine exposes under-the-hood utilities for getting and setting properties bound with x-model
. This is useful for complex Alpine utilities that may want to override the default x-model behavior, or instances where you want to allow x-model
on a non-input element.
You can access these utilities through a property called _x_model
on the x-model
ed element. _x_model
has two methods to get and set the bound property:
el._x_model.get()
(returns the value of the bound property)el._x_model.set()
(sets the value of the bound property)
<div x-data="{ username: 'calebporzio' }">
<div x-ref="div" x-model="username"></div>
<button @click="$refs.div._x_model.set('phantomatrix')">
Change username to: 'phantomatrix'
</button>
<span x-text="$refs.div._x_model.get()"></span>
</div>
x-modelable
x-modelable
allows you to expose any Alpine property as the target of the x-model
directive.
Here's a simple example of using x-modelable
to expose a variable for binding with x-model
.
<div x-data="{ number: 5 }">
<div x-data="{ count: 0 }" x-modelable="count" x-model="number">
<button @click="count++">Increment</button>
</div>
Number: <span x-text="number"></span>
</div>
As you can see the outer scope property "number" is now bound to the inner scope property "count".
Typically this feature would be used in conjunction with a backend templating framework like Laravel Blade. It's useful for abstracting away Alpine components into backend templates and exposing state to the outside through x-model
as if it were a native input.
x-for
Alpine's x-for
directive allows you to create DOM elements by iterating through a list. Here's a simple example of using it to create a list of colors based on an array.
<ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }">
<template x-for="color in colors">
<li x-text="color"></li>
</template>
</ul>
You may also pass objects to x-for
.
<ul x-data="{ car: { make: 'Jeep', model: 'Grand Cherokee', color: 'Black' } }">
<template x-for="(value, index) in car">
<li>
<span x-text="index"></span>: <span x-text="value"></span>
</li>
</template>
</ul>
There are two rules worth noting about x-for
:
x-for
MUST be declared on a<template>
element. That<template>
element MUST contain only one root element
Keys
It is important to specify unique keys for each x-for
iteration if you are going to be re-ordering items. Without dynamic keys, Alpine may have a hard time keeping track of what re-orders and will cause odd side-effects.
<ul x-data="{
colors: [
{ id: 1, label: 'Red' },
{ id: 2, label: 'Orange' },
{ id: 3, label: 'Yellow' },]}">
<template x-for="color in colors" :key="color.id">
<li x-text="color.label"></li>
</template>
</ul>
Now if the colors are added, removed, re-ordered, or their "id"s change, Alpine will preserve or destroy the iterated <li>
elements accordingly.
Accessing indexes
If you need to access the index of each item in the iteration, you can do so using the ([item], [index]) in [items]
syntax like so:
<ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }">
<template x-for="(color, index) in colors">
<li>
<span x-text="index + ': '"></span>
<span x-text="color"></span>
</li>
</template>
</ul>
You can also access the index inside a dynamic :key
expression.
<template x-for="(color, index) in colors" :key="index">
Iterating over a range
If you need to simply loop n
number of times, rather than iterate through an array, Alpine offers a short syntax.
<ul>
<template x-for="i in 10">
<li x-text="i"></li>
</template>
</ul>
i
in this case can be named anything you like.
Contents of a <template>
As mentioned above, an <template>
tag must contain only one root element.
For example, the following code will not work:
<template x-for="color in colors">
<span>The next color is </span><span x-text="color">
</template>
but this code will work:
<template x-for="color in colors">
<p><span>The next color is </span><span x-text="color"></p>
</template>
x-transition
Alpine provides a robust transitions utility out of the box. With a few x-transition
directives, you can create smooth transitions between when an element is shown or hidden.
There are two primary ways to handle transitions in Alpine:
The transition helper
The simplest way to achieve a transition using Alpine is by adding x-transition
to an element with x-show
on it. For example:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div x-show="open" x-transition>Hello 👋</div>
</div>
Toggle
As you can see, by default, x-transition
applies pleasant transition defaults to fade and scale the revealing element.
You can override these defaults with modifiers attached to x-transition
. Let's take a look at those.
Customizing duration
Initially, the duration is set to be 150 milliseconds when entering, and 75 milliseconds when leaving.
You can configure the duration you want for a transition with the .duration
modifier:
<div ... x-transition.duration.500ms>
The above <div>
will transition for 500 milliseconds when entering, and 500 milliseconds when leaving.
If you wish to customize the durations specifically for entering and leaving, you can do that like so:
<div ...
x-transition:enter.duration.500ms
x-transition:leave.duration.400ms>
Customizing delay
You can delay a transition using the .delay
modifier like so:
<div ... x-transition.delay.50ms>
The above example will delay the transition and in and out of the element by 50 milliseconds.
Customizing opacity
By default, Alpine's x-transition
applies both a scale and opacity transition to achieve a "fade" effect.
If you wish to only apply the opacity transition (no scale), you can accomplish that like so:
<div ... x-transition.opacity>
Customizing scale
Similar to the .opacity
modifier, you can configure x-transition
to ONLY scale (and not transition opacity as well) like so:
<div ... x-transition.scale>
The .scale
modifier also offers the ability to configure its scale values AND its origin values:
<div ... x-transition.scale.80>
The above snippet will scale the element up and down by 80%.
Again, you may customize these values separately for enter and leaving transitions like so:
<div ... x-transition:enter.scale.80 x-transition:leave.scale.90>
To customize the origin of the scale transition, you can use the .origin
modifier:
<div ... x-transition.scale.origin.top>
Now the scale will be applied using the top of the element as the origin, instead of the center by default.
Like you may have guessed, the possible values for this customization are: top
, bottom
, left
, and right
.
If you wish, you can also combine two origin values. For example, if you want the origin of the scale to be "top right", you can use: .origin.top.right
as the modifier.
Applying CSS classes
For direct control over exactly what goes into your transitions, you can apply CSS classes at different stages of the transition.
The following examples use TailwindCSS utility classes.
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div
x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90">Hello 👋</div>
</div>
Directive | Description |
---|---|
:enter |
Applied during the entire entering phase. |
:enter-start |
Added before element is inserted, removed one frame after element is inserted. |
:enter-end |
Added one frame after element is inserted (at the same time enter-start is removed), removed when transition/animation finishes. |
:leave |
Applied during the entire leaving phase. |
:leave-start |
Added immediately when a leaving transition is triggered, removed after one frame. |
:leave-end |
Added one frame after a leaving transition is triggered (at the same time leave-start is removed), removed when the transition/animation finishes. |
x-effect
x-effect
is a useful directive for re-evaluating an expression when one of its dependencies change. You can think of it as a watcher where you don't have to specify what property to watch, it will watch all properties used within it.
If this definition is confusing for you, that's ok. It's better explained through an example:
<div x-data="{ label: 'Hello' }" x-effect="console.log(label)">
<button @click="label += ' World!'">Change Message</button>
</div>
When this component is loaded, the x-effect
expression will be run and "Hello" will be logged into the console.
Because Alpine knows about any property references contained within x-effect
, when the button is clicked and label
is changed, the effect will be re-triggered and "Hello World!" will be logged to the console.
x-ignore
By default, Alpine will crawl and initialize the entire DOM tree of an element containing x-init
or x-data
.
If for some reason, you don't want Alpine to touch a specific section of your HTML, you can prevent it from doing so using x-ignore
.
<div x-data="{ label: 'From Alpine' }">
<div x-ignore>
<span x-text="label"></span>
</div>
</div>
In the above example, the <span>
tag will not contain "From Alpine" because we told Alpine to ignore the contents of the div
completely.
x-ref
x-ref
in combination with $refs
is a useful utility for easily accessing DOM elements directly. It's most useful as a replacement for APIs like getElementById
and querySelector
.
<button @click="$refs.text.remove()">Remove Text</button>
<span x-ref="text">Hello 👋</span>
x-cloak
Sometimes, when you're using AlpineJS for a part of your template, there is a "blip" where you might see your uninitialized template after the page loads, but before Alpine loads.
x-cloak
addresses this scenario by hiding the element it's attached to until Alpine is fully loaded on the page.
For x-cloak
to work however, you must add the following CSS to the page.
[x-cloak] { display: none !important; }
The following example will hide the <span>
tag until its x-show
is specifically set to true, preventing any "blip" of the hidden element onto screen as Alpine loads.
<span x-cloak x-show="false">This will not 'blip' onto screen at any point</span>
x-cloak
doesn't just work on elements hidden by x-show
or x-if
: it also ensures that elements containing data are hidden until the data is correctly set. The following example will hide the <span>
tag until Alpine has set its text content to the message
property.
<span x-cloak x-text="message"></span>
When Alpine loads on the page, it removes all x-cloak
property from the element, which also removes the display: none;
applied by CSS, therefore showing the element.
Alternative to global syntax
If you'd like to achieve this same behavior, but avoid having to include a global style, you can use the following cool, but admittedly odd trick:
<template x-if="true"> <span x-text="message"></span></template>
This will achieve the same goal as x-cloak
by just leveraging the way x-if
works.
Because <template>
elements are "hidden" in browsers by default, you won't see the <span>
until Alpine has had a chance to render the x-if="true"
and show it.
Again, this solution is not for everyone, but it's worth mentioning for special cases.
x-teleport
The x-teleport
directive allows you to transport part of your Alpine template to another part of the DOM on the page entirely.
This is useful for things like modals (especially nesting them), where it's helpful to break out of the z-index of the current Alpine component.
x-teleport
By attaching x-teleport
to a <template>
element, you are telling Alpine to "append" that element to the provided selector.
The
x-teleport
selector can be any string you would normally pass into something likedocument.querySelector
. It will find the first element that matches, be it a tag name (body
), class name (.my-class
), ID (#my-id
), or any other valid CSS selector.
Here's a contrived modal example:
<body>
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle Modal</button>
<template x-teleport="body">
<div x-show="open">Modal contents...</div>
</template>
</div>
<div>Some other content placed AFTER the modal markup.</div>
...
</body>
Notice how when toggling the modal, the actual modal contents show up AFTER the "Some other content..." element? This is because when Alpine is initializing, it sees x-teleport="body"
and appends and initializes that element to the provided element selector.
Forwarding events
Alpine tries its best to make the experience of teleporting seamless. Anything you would normally do in a template, you should be able to do inside an x-teleport
template. Teleported content can access the normal Alpine scope of the component as well as other features like $refs
, $root
, etc...
However, native DOM events have no concept of teleportation, so if, for example, you trigger a "click" event from inside a teleported element, that event will bubble up the DOM tree as it normally would.
To make this experience more seamless, you can "forward" events by simply registering event listeners on the <template x-teleport...>
element itself like so:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle Modal</button>
<template x-teleport="body" @click="open = false">
<div x-show="open">Modal contents... (click to close)</div>
</template>
</div>
Notice how we are now able to listen for events dispatched from within the teleported element from outside the <template>
element itself?
Alpine does this by looking for event listeners registered on <template x-teleport...>
and stops those events from propagating past the live, teleported, DOM element. Then, it creates a copy of that event and re-dispatches it from <template x-teleport...>
.
Nesting
Teleporting is especially helpful if you are trying to nest one modal within another. Alpine makes it simple to do so:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle Modal</button>
<template x-teleport="body">
<div x-show="open">
Modal contents...
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle Nested Modal</button>
<template x-teleport="body">
<div x-show="open">Nested modal contents...</div>
</template>
</div>
</div>
</template>
</div>
After toggling "on" both modals, they are authored as children, but will be rendered as sibling elements on the page, not within one another.
x-if
x-if
is used for toggling elements on the page, similarly to x-show
, however it completely adds and removes the element it's applied to rather than just changing its CSS display property to "none".
Because of this difference in behavior, x-if
should not be applied directly to the element, but instead to a <template>
tag that encloses the element. This way, Alpine can keep a record of the element once it's removed from the page.
<template x-if="open">
<div>Contents...</div>
</template>
Unlike
x-show
,x-if
, does NOT support transitioning toggles withx-transition
.
Remember:
<template>
tags can only contain one root level element.
x-id
x-id
allows you to declare a new "scope" for any new IDs generated using $id()
. It accepts an array of strings (ID names) and adds a suffix to each $id('...')
generated within it that is unique to other IDs on the page.
x-id
is meant to be used in conjunction with the $id(...)
magic.
Visit the $id documentation for a better understanding of this feature.
Here's a brief example of this directive in use:
<div x-id="['text-input']">
<label :for="$id('text-input')">Username</label>
<!-- for="text-input-1" -->
<input type="text" :id="$id('text-input')">
<!-- id="text-input-1" -->
</div>
<div x-id="['text-input']">
<label :for="$id('text-input')">Username</label>
<!-- for="text-input-2" -->
<input type="text" :id="$id('text-input')">
<!-- id="text-input-2" -->
</div>
Magics
$el
$el
is a magic property that can be used to retrieve the current DOM node.
<button @click="$el.innerHTML = 'Hello World!'">Replace me with "Hello World!"</button>
$refs
$refs
is a magic property that can be used to retrieve DOM elements marked with x-ref
inside the component. This is useful when you need to manually manipulate DOM elements. It's often used as a more succinct, scoped, alternative to document.querySelector
.
<button @click="$refs.text.remove()">Remove Text</button> <span x-ref="text">Hello 👋</span>
Remove Text
Hello 👋
Now, when the <button>
is pressed, the <span>
will be removed.
Limitations
In V2 it was possible to bind $refs
to elements dynamically, like seen below:
<template x-for="item in items" :key="item.id" > <div :x-ref="item.name"> some content ... </div></template>
However, in V3, $refs
can only be accessed for elements that are created statically. So for the example above: if you were expecting the value of item.name
inside of $refs
to be something like Batteries, you should be aware that $refs
will actually contain the literal string 'item.name'
and not Batteries.
$store
You can use $store
to conveniently access global Alpine stores registered using Alpine.store(...)
. For example:
<button x-data @click="$store.darkMode.toggle()">Toggle Dark Mode</button> ... <div x-data :class="$store.darkMode.on && 'bg-black'"> ...</div> <script> document.addEventListener('alpine:init', () => { Alpine.store('darkMode', { on: false, toggle() { this.on = ! this.on } }) })</script>
Given that we've registered the darkMode
store and set on
to "false", when the <button>
is pressed, on
will be "true" and the background color of the page will change to black.
Single-value stores
If you don't need an entire object for a store, you can set and use any kind of data as a store.
Here's the example from above but using it more simply as a boolean value:
<button x-data @click="$store.darkMode = ! $store.darkMode">Toggle Dark Mode</button> ... <div x-data :class="$store.darkMode && 'bg-black'"> ...</div> <script> document.addEventListener('alpine:init', () => { Alpine.store('darkMode', false) })</script>
→ Read more about Alpine stores
$watch
You can "watch" a component property using the $watch
magic method. For example:
<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))"> <button @click="open = ! open">Toggle Open</button></div>
In the above example, when the button is pressed and open
is changed, the provided callback will fire and console.log
the new value:
You can watch deeply nested properties using "dot" notation
<div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo.bar', value => console.log(value))"> <button @click="foo.bar = 'bob'">Toggle Open</button></div>
When the <button>
is pressed, foo.bar
will be set to "bob", and "bob" will be logged to the console.
Getting the "old" value
$watch
keeps track of the previous value of the property being watched, You can access it using the optional second argument to the callback like so:
<div x-data="{ open: false }" x-init="$watch('open', (value, oldValue) => console.log(value, oldValue))"> <button @click="open = ! open">Toggle Open</button></div>
Deep watching
$watch
automatically watches from changes at any level but you should keep in mind that, when a change is detected, the watcher will return the value of the observed property, not the value of the subproperty that has changed.
<div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo', (value, oldValue) => console.log(value, oldValue))"> <button @click="foo.bar = 'bob'">Update</button></div>
When the <button>
is pressed, foo.bar
will be set to "bob", and "{bar: 'bob'} {bar: 'baz'}" will be logged to the console (new and old value).
⚠️ Changing a property of a "watched" object as a side effect of the
$watch
callback will generate an infinite loop and eventually error.
<!-- 🚫 Infinite loop --><div x-data="{ foo: { bar: 'baz', bob: 'lob' }}" x-init="$watch('foo', value => foo.bob = foo.bar)"> <button @click="foo.bar = 'bob'">Update</button></div>
$dispatch
$dispatch
is a helpful shortcut for dispatching browser events.
<div @notify="alert('Hello World!')"> <button @click="$dispatch('notify')"> Notify </button></div>
Notify
You can also pass data along with the dispatched event if you wish. This data will be accessible as the .detail
property of the event:
<div @notify="alert($event.detail.message)"> <button @click="$dispatch('notify', { message: 'Hello World!' })"> Notify </button></div>
Notify
Under the hood, $dispatch
is a wrapper for the more verbose API: element.dispatchEvent(new CustomEvent(...))
Note on event propagation
Notice that, because of event bubbling, when you need to capture events dispatched from nodes that are under the same nesting hierarchy, you'll need to use the .window
modifier:
Example:
<!-- 🚫 Won't work --><div x-data> <span @notify="..."></span> <button @click="$dispatch('notify')">Notify</button></div> <!-- ✅ Will work (because of .window) --><div x-data> <span @notify.window="..."></span> <button @click="$dispatch('notify')">Notify</button></div>
The first example won't work because when
notify
is dispatched, it'll propagate to its common ancestor, thediv
, not its sibling, the<span>
. The second example will work because the sibling is listening fornotify
at thewindow
level, which the custom event will eventually bubble up to.
Dispatching to other components
You can also take advantage of the previous technique to make your components talk to each other:
Example:
<div x-data="{ title: 'Hello' }" @set-title.window="title = $event.detail"> <h1 x-text="title"></h1></div> <div x-data> <button @click="$dispatch('set-title', 'Hello World!')">Click me</button></div><!-- When clicked, the content of the h1 will set to "Hello World!". -->
Dispatching to x-model
You can also use $dispatch()
to trigger data updates for x-model
data bindings. For example:
<div x-data="{ title: 'Hello' }"> <span x-model="title"> <button @click="$dispatch('input', 'Hello World!')">Click me</button> <!-- After the button is pressed, `x-model` will catch the bubbling "input" event, and update title. --> </span></div>
This opens up the door for making custom input components whose value can be set via x-model
.
$nextTick
$nextTick
is a magic property that allows you to only execute a given expression AFTER Alpine has made its reactive DOM updates. This is useful for times you want to interact with the DOM state AFTER it's reflected any data updates you've made.
<div x-data="{ title: 'Hello' }"> <button @click=" title = 'Hello World!'; $nextTick(() => { console.log($el.innerText) }); " x-text="title" ></button></div>
In the above example, rather than logging "Hello" to the console, "Hello World!" will be logged because $nextTick
was used to wait until Alpine was finished updating the DOM.
Promises
$nextTick
returns a promise, allowing the use of $nextTick
to pause an async function until after pending dom updates. When used like this, $nextTick
also does not require an argument to be passed.
<div x-data="{ title: 'Hello' }"> <button @click=" title = 'Hello World!'; await $nextTick(); console.log($el.innerText); " x-text="title" ></button></div>
$root
$root
is a magic property that can be used to retrieve the root element of any Alpine component. In other words the closest element up the DOM tree that contains x-data
.
<div x-data data-message="Hello World!"> <button @click="alert($root.dataset.message)">Say Hi</button></div>
$data
$data
is a magic property that gives you access to the current Alpine data scope (generally provided by x-data
).
Most of the time, you can just access Alpine data within expressions directly. for example x-data="{ message: 'Hello Caleb!' }"
will allow you to do things like x-text="message"
.
However, sometimes it is helpful to have an actual object that encapsulates all scope that you can pass around to other functions:
<div x-data="{ greeting: 'Hello' }"> <div x-data="{ name: 'Caleb' }"> <button @click="sayHello($data)">Say Hello</button> </div></div> <script> function sayHello({ greeting, name }) { alert(greeting + ' ' + name + '!') }</script>
Say Hello
Now when the button is pressed, the browser will alert Hello Caleb!
because it was passed a data object that contained all the Alpine scope of the expression that called it (@click="..."
).
Most applications won't need this magic property, but it can be very helpful for deeper, more complicated Alpine utilities.
$id
$id
is a magic property that can be used to generate an element's ID and ensure that it won't conflict with other IDs of the same name on the same page.
This utility is extremely helpful when building re-usable components (presumably in a back-end template) that might occur multiple times on a page, and make use of ID attributes.
Things like input components, modals, listboxes, etc. will all benefit from this utility.
Basic usage
Suppose you have two input elements on a page, and you want them to have a unique ID from each other, you can do the following:
<input type="text" :id="$id('text-input')"><!-- id="text-input-1" --> <input type="text" :id="$id('text-input')"><!-- id="text-input-2" -->
As you can see, $id
takes in a string and spits out an appended suffix that is unique on the page.
Grouping with x-id
Now let's say you want to have those same two input elements, but this time you want <label>
elements for each of them.
This presents a problem, you now need to be able to reference the same ID twice. One for the <label>
's for
attribute, and the other for the id
on the input.
Here is a way that you might think to accomplish this and is totally valid:
<div x-data="{ id: $id('text-input') }"> <label :for="id"> <!-- "text-input-1" --> <input type="text" :id="id"> <!-- "text-input-1" --></div> <div x-data="{ id: $id('text-input') }"> <label :for="id"> <!-- "text-input-2" --> <input type="text" :id="id"> <!-- "text-input-2" --></div>
This approach is fine, however, having to name and store the ID in your component scope feels cumbersome.
To accomplish this same task in a more flexible way, you can use Alpine's x-id
directive to declare an "id scope" for a set of IDs:
<div x-id="['text-input']"> <label :for="$id('text-input')"> <!-- "text-input-1" --> <input type="text" :id="$id('text-input')"> <!-- "text-input-1" --></div> <div x-id="['text-input']"> <label :for="$id('text-input')"> <!-- "text-input-2" --> <input type="text" :id="$id('text-input')"> <!-- "text-input-2" --></div>
As you can see, x-id
accepts an array of ID names. Now any usages of $id()
within that scope, will all use the same ID. Think of them as "id groups".
Nesting
As you might have intuited, you can freely nest these x-id
groups, like so:
<div x-id="['text-input']"> <label :for="$id('text-input')"> <!-- "text-input-1" --> <input type="text" :id="$id('text-input')"> <!-- "text-input-1" --> <div x-id="['text-input']"> <label :for="$id('text-input')"> <!-- "text-input-2" --> <input type="text" :id="$id('text-input')"> <!-- "text-input-2" --> </div></div>
Keyed IDs (For Looping)
Sometimes, it is helpful to specify an additional suffix on the end of an ID for the purpose of identifying it within a loop.
For this, $id()
accepts an optional second parameter that will be added as a suffix on the end of the generated ID.
A common example of this need is something like a listbox component that uses the aria-activedescendant
attribute to tell assistive technologies which element is "active" in the list:
<ul x-id="['list-item']" :aria-activedescendant="$id('list-item', activeItem.id)"> <template x-for="item in items" :key="item.id"> <li :id="$id('list-item', item.id)">...</li> </template></ul>
This is an incomplete example of a listbox, but it should still be helpful to demonstrate a scenario where you might need each ID in a group to still be unique to the page, but also be keyed within a loop so that you can reference individual IDs within that group.
Globals
Alpine.data
Alpine.data(...)
provides a way to re-use x-data
contexts within your application.
Here's a contrived dropdown
component for example:
<div x-data="dropdown"> <button @click="toggle">...</button> <div x-show="open">...</div></div> <script> document.addEventListener('alpine:init', () => { Alpine.data('dropdown', () => ({ open: false, toggle() { this.open = ! this.open } })) })</script>
As you can see we've extracted the properties and methods we would usually define directly inside x-data
into a separate Alpine component object.
Registering from a bundle
If you've chosen to use a build step for your Alpine code, you should register your components in the following way:
import Alpine from 'alpinejs'import dropdown from './dropdown.js' Alpine.data('dropdown', dropdown) Alpine.start()
This assumes you have a file called dropdown.js
with the following contents:
export default () => ({ open: false, toggle() { this.open = ! this.open }})
Initial parameters
In addition to referencing Alpine.data
providers by their name plainly (like x-data="dropdown"
), you can also reference them as functions (x-data="dropdown()"
). By calling them as functions directly, you can pass in additional parameters to be used when creating the initial data object like so:
<div x-data="dropdown(true)">
Alpine.data('dropdown', (initialOpenState = false) => ({ open: initialOpenState}))
Now, you can re-use the dropdown
object, but provide it with different parameters as you need to.
Init functions
If your component contains an init()
method, Alpine will automatically execute it before it renders the component. For example:
Alpine.data('dropdown', () => ({ init() { // This code will be executed before Alpine // initializes the rest of the component. }}))
Destroy functions
If your component contains a destroy()
method, Alpine will automatically execute it before cleaning up the component.
A primary example for this is when registering an event handler with another library or a browser API that isn't available through Alpine. See the following example code on how to use the destroy()
method to clean up such a handler.
Alpine.data('timer', () => ({ timer: null, counter: 0, init() { // Register an event handler that references the component instance this.timer = setInterval(() => { console.log('Increased counter to', ++this.counter); }, 1000); }, destroy() { // Detach the handler, avoiding memory and side-effect leakage clearInterval(this.timer); },}))
An example where a component is destroyed is when using one inside an x-if
:
<span x-data="{ enabled: false }"> <button @click.prevent="enabled = !enabled">Toggle</button> <template x-if="enabled"> <span x-data="timer" x-text="counter"></span> </template></span>
Using magic properties
If you want to access magic methods or properties from a component object, you can do so using the this
context:
Alpine.data('dropdown', () => ({ open: false, init() { this.$watch('open', () => {...}) }}))
Encapsulating directives with x-bind
If you wish to re-use more than just the data object of a component, you can encapsulate entire Alpine template directives using x-bind
.
The following is an example of extracting the templating details of our previous dropdown component using x-bind
:
<div x-data="dropdown"> <button x-bind="trigger"></button> <div x-bind="dialogue"></div></div>
Alpine.data('dropdown', () => ({ open: false, trigger: { ['@click']() { this.open = ! this.open }, }, dialogue: { ['x-show']() { return this.open }, },}))
Alpine.store
Alpine offers global state management through the Alpine.store()
API.
Registering A Store
You can either define an Alpine store inside of an alpine:init
listener (in the case of including Alpine via a <script>
tag), OR you can define it before manually calling Alpine.start()
(in the case of importing Alpine into a build):
From a script tag:
<script> document.addEventListener('alpine:init', () => { Alpine.store('darkMode', { on: false, toggle() { this.on = ! this.on } }) })</script>
From a bundle:
import Alpine from 'alpinejs' Alpine.store('darkMode', { on: false, toggle() { this.on = ! this.on }}) Alpine.start()
Accessing stores
You can access data from any store within Alpine expressions using the $store
magic property:
<div x-data :class="$store.darkMode.on && 'bg-black'">...</div>
You can also modify properties within the store and everything that depends on those properties will automatically react. For example:
<button x-data @click="$store.darkMode.toggle()">Toggle Dark Mode</button>
Additionally, you can access a store externally using Alpine.store()
by omitting the second parameter like so:
<script> Alpine.store('darkMode').toggle()</script>
Initializing stores
If you provide init()
method in an Alpine store, it will be executed right after the store is registered. This is useful for initializing any state inside the store with sensible starting values.
<script> document.addEventListener('alpine:init', () => { Alpine.store('darkMode', { init() { this.on = window.matchMedia('(prefers-color-scheme: dark)').matches }, on: false, toggle() { this.on = ! this.on } }) })</script>
Notice the newly added init()
method in the example above. With this addition, the on
store variable will be set to the browser's color scheme preference before Alpine renders anything on the page.
Single-value stores
If you don't need an entire object for a store, you can set and use any kind of data as a store.
Here's the example from above but using it more simply as a boolean value:
<button x-data @click="$store.darkMode = ! $store.darkMode">Toggle Dark Mode</button> ... <div x-data :class="$store.darkMode && 'bg-black'"> ...</div> <script> document.addEventListener('alpine:init', () => { Alpine.store('darkMode', false) })</script>
Alpine.bind
Alpine.bind(...)
provides a way to re-use x-bind
objects within your application.
Here's a simple example. Rather than binding attributes manually with Alpine:
<button type="button" @click="doSomething()" :disabled="shouldDisable"></button>
You can bundle these attributes up into a reusable object and use x-bind
to bind to that:
<button x-bind="SomeButton"></button> <script> document.addEventListener('alpine:init', () => { Alpine.bind('SomeButton', () => ({ type: 'button', '@click'() { this.doSomething() }, ':disabled'() { return this.shouldDisable }, })) })</script>