Description: Enable decoupled communication where components emit custom events (using $dispatch()) that other components (parents, siblings, or global listeners) can react to, promoting modularity and reducing direct component dependencies.
$dispatch('eventName', detailObject)
'eventName': A string representing the unique name for your custom event. For example, 'item-selected', 'cart-updated', or 'search-query-updated'. Event names are typically written in kebab-case (e.g., my-custom-event).detailObject (optional): A JavaScript object that serves as the payload for the event. This data can be accessed by any component listening for this event. For example, { itemId: 123, quantity: 2 }. If omitted, no extra data is sent beyond the event itself.<!-- Example of dispatching an event -->
<button @click="$dispatch('user-action', { action: 'loggedIn', userId: 'user_123' })"
class="bg-blue-500 text-white p-2 rounded">
Dispatch 'user-action'
</button>
@event-name.modifier="handler($event.detail)" (or x-on:event-name.modifier)
event-name: The name of the event to listen for (e.g., user-action, click, input). For custom events dispatched by $dispatch, this matches the 'eventName' string..modifier (optional): Alpine.js offers various event modifiers that alter how the event listener behaves:
.window: Listens for the event on the global window object. Crucial for inter-component communication where components are not in a direct parent-child relationship..document: Listens on the global document object..prevent: Calls event.preventDefault() on the native event..stop: Calls event.stopPropagation(), preventing the event from bubbling further up the DOM tree..once: The event handler will only be triggered once..outside (for clicks outside an element), .self (handler only runs if event originated on this element), .debounce, and .throttle.handler($event.detail): The JavaScript expression or a method within your Alpine component's data scope that gets executed when the event is caught.
$event: This is a special Alpine magic property that provides access to the native browser Event object (e.g., MouseEvent, CustomEvent).$event.detail: For custom events dispatched with a payload (the detailObject), this property holds that payload. This is the standard way custom event data is accessed.<!-- Example of listening for a custom event on the window -->
<div x-data="{ lastUserAction: 'No action yet' }"
@user-action.window="lastUserAction = `User performed: '${$event.detail.action}' (ID: ${$event.detail.userId})`"
class="p-2 border rounded">
Last action: <span x-text="lastUserAction" class="font-semibold"></span>
</div>
.window modifier
.window modifier changes this behavior by attaching the event listener directly to the global window object.$dispatch, as long as the listener uses @event-name.window, regardless of its DOM position relative to the dispatching component.Using the .window modifier is crucial for global events or when the listening component is not a direct ancestor of the dispatching component.
Without .window, the event only bubbles up the DOM tree from the dispatching HTML element. This means it might not reach listeners on sibling components or other unrelated components elsewhere on the page.
Scenario: Component A dispatches an event. Component B is a sibling of Component A (i.e., they share the same parent but A is not an ancestor of B). Component C is a child of Component A.
@my-event="..." because the event bubbles up to it from A.@my-event="..." because the event from A does not bubble to B. Component B must use @my-event.window="..." to catch the event globally.Python developers new to front-end eventing might assume events are globally broadcast by default. In the DOM, they are typically scoped by their propagation path (bubbling/capturing). Alpine's .window modifier provides a straightforward way to achieve global reach when needed.
Event detail ($event.detail) is where the payload sent with $dispatch is found.
When you dispatch an event with a data payload like $dispatch('my-event', { info: 'some data' }), that payload (which is { info: 'some data' } in this case) is not directly attached to the $event object itself. Instead, it's nested within the $event.detail property.
This is a standard convention for browser CustomEvents, which Alpine.js leverages for its $dispatch mechanism.
Incorrect access (will likely result in undefined):
// Inside an event handler
let myData = $event.info; // Incorrect!
let myData = $event.payload.info; // Also incorrect!
Correct access:
// Inside an event handler
let myData = $event.detail.info; // Correct! $event.detail is the payload object.
Beginners might intuitively look for the custom data directly on $event, leading to undefined errors or unexpected behavior. Always remember to access the dispatched payload via $event.detail.
This example demonstrates a global search input that dispatches a custom event (search-query-updated). Other independent components on the page listen for this event using the .window modifier to react to search term changes.
Dispatches 'search-query-updated' event globally with payload: { query: '' }
Listens for 'search-query-updated.window' and updates text based on $event.detail.query.
Also listens for 'search-query-updated.window' to show a different kind of UI update, demonstrating multiple independent listeners.
This component attempts to listen to 'search-query-updated' without the .window modifier. It will not receive events dispatched from the global search input above because it's not an ancestor of the dispatching element, nor is the event specifically targeted at it and bubbling. This highlights why .window is essential for global or sibling communication.