Description: Enable communication between different Alpine components, including parent-child, child-parent, or sibling components, by sending and listening for custom browser events.
In AlpineJS, custom events are a cornerstone for enabling communication between different parts of your user interface. This is particularly useful when you have nested components (parent-child relationships) or even components that are siblings in the DOM tree. Python developers can think of this as a publish-subscribe system or a signal/slot mechanism built into the browser's event system, which AlpineJS makes easy to use.
$dispatch('eventName', payloadObject)This magic property is available within your Alpine component's scope. You use it to send or "dispatch" a custom browser event.
'eventName': A string representing the name of your custom event (e.g., 'item-selected', 'form-submitted').payloadObject: An optional JavaScript object containing data you want to send with the event. AlpineJS automatically makes this object available on event.detail for any listeners. For example: this.$dispatch('user-updated', { userId: 123, newName: 'Alex' }).By default, events dispatched with $dispatch will "bubble" up the DOM tree. This means parent elements (and their Alpine components) can listen for events triggered by their children.
// Inside an Alpine component's method or x-on handler:
this.$dispatch('task-completed', { taskId: 't42', status: 'done' });
x-on:eventName="handleEvent" (or @eventName="handleEvent")This directive is used to listen for browser events on an HTML element. For custom events dispatched by $dispatch:
eventName: Must match the name of the event you dispatched.handleEvent: Can be a JavaScript expression or a method name defined in your Alpine component's data. The actual browser Event object is automatically available as event (or $event if inside an expression that might conflict with a data property named 'event').This is typically used on a parent component to listen for events from its children.
<!-- Parent Component -->
<div x-data="{ lastTask: '' }" x-on:task-completed="lastTask = event.detail.taskId + ' is ' + event.detail.status">
<p>Last task update: <span x-text="lastTask"></span></p>
<!-- Child Component that might dispatch 'task-completed' -->
<div x-data>
<button @click="$dispatch('task-completed', { taskId: 't42', status: 'done' })">Complete Task</button>
</div>
</div>
x-on:eventName.window="handleGlobalEvent" (or @eventName.window="handleGlobalEvent")Sometimes, you need to listen for an event regardless of where it originated in the DOM, or communicate between components that don't have a direct parent-child relationship (e.g., siblings, or components in entirely different parts of the page). The .window modifier tells AlpineJS to attach the event listener to the global window object instead of the current element.
This is useful for:
window object (e.g., window.dispatchEvent(new CustomEvent('my-global-event', { detail: data }))).$dispatch from any component, provided they bubble up to the window (which they do by default if not stopped).<!-- Component A (could be anywhere on the page) -->
<div x-data>
<button @click="$dispatch('global-announcement', { message: 'Hello everyone!' })">Announce Globally</button>
</div>
<!-- Component B (also anywhere, listening globally) -->
<div x-data="{ announcement: '' }" @global-announcement.window="announcement = event.detail.message">
<p>Global Announcement: <span x-text="announcement"></span></p>
</div>
event.detailWhen you dispatch an event with a data payload using $dispatch('eventName', { some: 'data' }), AlpineJS ensures this payload is accessible to listeners via the event.detail property. This is part of the standard DOM CustomEvent API.
// Dispatcher:
this.$dispatch('item-info', { id: 10, name: 'Alpine Book' });
// Listener:
// In an Alpine method: handleItemInfo(event) { let item = event.detail; console.log(item.id, item.name); }
// In an x-on attribute: @item-info="console.log(event.detail.id, event.detail.name)"
Always remember to access your custom data through event.detail.
Events dispatched with $dispatch only bubble up the DOM tree by default.
If sibling components (elements at the same level in the DOM, not parent-child) need to communicate, standard $dispatch from one sibling won't be directly heard by the other unless they share a common ancestor component that listens and perhaps relays the message.
For direct sibling-to-sibling or communication between distant components without a convenient common parent, you have two main strategies:
<body>) do a $dispatch. Other components can listen with @myevent.window="handler". The event bubbles from the high-level component's element to the document and window.window.dispatchEvent(new CustomEvent('my-global-event', { detail: { some: 'data' } })). Other components would then listen with @my-global-event.window="handler".Forgetting to access data via event.detail.
When you dispatch an event with data, e.g., $dispatch('my-event', { user: 'Alice', score: 100 }), the listener function receives a standard Event object. The custom data you sent is always nested inside the event.detail property.
A common mistake is trying to access event.user or event.data.user. It's always event.detail.user.
// Correct way to access data in a handler:
function handleMyEvent(event) {
console.log(event.detail.user); // 'Alice'
console.log(event.detail.score); // 100
}
Overusing global window events for localized component communication.
While .window listeners are powerful for cross-cutting concerns (like notifications, theme changes, or session updates), using them excessively for communication that could be handled by direct parent-child event bubbling can make your application's data flow harder to trace and debug. It's akin to overusing global variables in Python; it works, but can lead to a tangled web of interactions.
For communication between a child and its immediate parent, or vice-versa (parent passing data via props, child emitting events), prefer the standard $dispatch and direct listeners on the parent. Reserve window events for when components are genuinely decoupled or too far apart in the DOM for simple bubbling to be practical. Choose the appropriate tool for the job – sometimes a targeted signal (bubbling event) is better than a global broadcast (window event).
Status:
No 'item-notified' events caught yet.
Last Global Message:
This event is dispatched by the main component and caught by its own .window listener.
No 'app-notification' events processed yet.
Last Direct Window Message:
This event is dispatched using window.dispatchEvent and caught by a .window listener.
No 'system-wide-alert' events processed yet.