Alpine.js Cheat Sheet
A quick reference for Alpine.js directives like x-data, x-show, and x-for, plus magic properties for lightweight interactivity.
x-data & Basic Directives
Declaring component state and binding it to the DOM.
<div x-data="{ open: false, count: 0 }"> <button @click="open = !open">Toggle</button> <div x-show="open">Content</div> <button @click="count++">Increment</button> <span x-text="count"></span></div>
Loops & Conditionals
Rendering lists and toggling elements in and out of the DOM.
<ul x-data="{ items: ['a', 'b', 'c'] }"> <template x-for="(item, index) in items" :key="index"> <li x-text="item"></li> </template></ul><template x-if="loggedIn"> <p>Welcome back!</p></template>
Core Directives
The attributes Alpine reads to add behavior to plain HTML.
- x-data- declares a component's reactive state as a plain JS object
- x-show- toggles CSS display based on an expression; the element stays in the DOM
- x-if- adds/removes the element from the DOM entirely; must wrap a <template> tag
- x-model- two-way binds a form input to a piece of x-data state
- x-bind (:attr)- dynamically binds an HTML attribute to an expression
- x-on (@event)- attaches an event listener that runs an expression
- x-text / x-html- sets an element's text content or innerHTML from an expression
- x-transition- applies CSS transition classes when an element toggles via x-show/x-if
Magic Properties
Built-in helpers available inside any Alpine expression.
<div x-data="{ open: false }" @click.outside="open = false"> <button @click="open = true" x-ref="trigger">Open</button> <div x-show="open" x-init="$watch('open', value => console.log('open changed', value))"> <input x-init="$el.focus()" /> </div></div><!-- $el: current element, $refs: named elements via x-ref, $watch: observe a value -->
Alpine.store() Global State
Sharing reactive state across components without prop drilling using a global store.
<script> document.addEventListener('alpine:init', () => { Alpine.store('cart', { items: [], add(item) { this.items.push(item) }, get total() { return this.items.reduce((s, i) => s + i.price, 0) } }) })</script><button @click="$store.cart.add({ name: 'Book', price: 12 })">Add</button><span x-text="$store.cart.total"></span><!-- $store is reactive: any component reading it re-renders on mutation -->
Alpine.data() Reusable Components
Extracting x-data logic into a named, reusable component definition instead of inline objects.
<script> document.addEventListener('alpine:init', () => { Alpine.data('dropdown', () => ({ open: false, toggle() { this.open = !this.open }, init() { this.$watch('open', v => v && this.$nextTick(() => this.$refs.menu.focus())) } })) })</script><div x-data="dropdown"> <button @click="toggle">Menu</button> <ul x-show="open" x-ref="menu" tabindex="-1">...</ul></div>
Custom Directives & Plugins
Registering a first-class Alpine.directive to encapsulate reusable DOM behavior.
Alpine.directive('tooltip', (el, { expression }, { evaluate, cleanup }) => { const text = evaluate(expression) const show = () => el.setAttribute('title', text) el.addEventListener('mouseenter', show) cleanup(() => el.removeEventListener('mouseenter', show))})// usage: <button x-tooltip="'Click to save'">Save</button>Alpine.magic('clipboard', () => (subject) => navigator.clipboard.writeText(subject))// usage: <button @click="$clipboard(text)">Copy</button>
Async Init & Fetch-Driven State
Populating x-data state from a network call and gating render until it resolves.
<div x-data="{ users: [], loading: true, async init() { const res = await fetch('/api/users') this.users = await res.json() this.loading = false } }"> <template x-if="loading"><p>Loading...</p></template> <template x-if="!loading"> <ul> <template x-for="user in users" :key="user.id"> <li x-text="user.name"></li> </template> </ul> </template></div>
Advanced Event & Binding Modifiers
Modifier chains that eliminate hand-written boilerplate for common interaction patterns.
- .debounce.500ms- delays event handler execution until the given idle period elapses (e.g. @input.debounce.500ms)
- .throttle.250ms- limits handler execution to at most once per interval, useful for scroll/resize
- .prevent / .stop- shorthand for event.preventDefault() / event.stopPropagation()
- .window / .document- attaches the listener to window/document instead of the element (@keydown.window.escape)
- .camel- forces an :attr binding to be set as a camelCase DOM property, not a lowercase attribute
- .self- only triggers the handler if the event originated on the element itself, not a child
- x-model.lazy / .number / .debounce- sync on change instead of input, cast to Number, or debounce two-way binding
Alpine re-evaluates expressions reactively but does not diff a virtual DOM -- keep x-data state small and colocated on the element it controls, and only lift it to a shared ancestor when multiple children genuinely need the same state.