langbrain/Alpine.js DOCS.md

28 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 of x-on:. This is a shorter, friendlier syntax that many prefer. From now on, this documentation will likely use @ instead of x-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 calling Alpine.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 with x-show, not with x-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 classstyledisabled, 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-ifx-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:

  1. The provided code will be run right away AND when data changes ($watch is "lazy" -- won't run until the first data change)
  2. 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', () => {
	//
})