You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello everyone,
While working with AlpineJS to build a UI library for Laravel, I noticed some limitations in the magic function $dispatch. Specifically:
It doesn't allow extended options.
It doesn’t support proper cancellation via preventDefault.
These limitations can lead to issues when building modular, event-driven components. Below, I explain the motivation and propose a minimal enhancement.
Problem Overview
Lack of Extendable Options
Currently, it's not possible to customize event behavior (e.g., disabling bubbles). This becomes problematic in cases like:
A modal and a dropdown both dispatching an event named closed.
When used together, the parent modal might unintentionally respond to the dropdown's event, causing conflicts.
Yes, we could rename events (e.g., modal:closed vs. dropdown:closed), but this doesn't solve issues with nested modals or more complex component structures.
Non-Cancelable in Practice
Although dispatched events are marked as cancelable: true, the function does not return the created CustomEvent.
As a result, it's not possible to check whether preventDefault() was called.
This prevents useful patterns like:
const event = $dispatch('close');
if (event) {
// Proceed to close the modal
}
This kind of control is useful for building modular components.
Proposed Solution
The required change is minimal:
Add support for a third parameter in $dispatch for passing native CustomEvent options (like bubbles, etc.).
Return the CustomEvent instance from $dispatch so developers can inspect and control event behavior.
Benefits
Prevents event conflicts across nested or reused components.
Enables advanced workflows like conditional UI actions based on event handling.
Maintains backward compatibility while giving developers more flexibility and control.
PR Review: #4608 — Add support for custom options and cancelable $dispatch
Type: Feature Verdict: Needs discussion
What's happening (plain English)
This PR makes two changes to $dispatch, both in packages/alpinejs/src/utils/dispatch.js:
Change 1 — Return the result. Currently $dispatch fires the event and returns nothing (undefined). This PR adds return so it returns what el.dispatchEvent() returns: true if no handler called preventDefault(), false if one did. This enables a pattern like:
// Before: impossible — $dispatch returns undefined (always falsy)// After: works — returns false only if a handler called preventDefault()if($dispatch('close')){open=false}
Change 2 — Options parameter. Adds a third parameter to $dispatch that gets spread into the CustomEvent constructor, letting you override defaults like bubbles:
// Event won't bubble to parent elements$dispatch('update',detail,{bubbles: false})
The implementation is just two lines changed in the source:
Return the event object instead of the boolean — Could do let e = new CustomEvent(...); el.dispatchEvent(e); return e so users get the full event (check e.defaultPrevented, read modified detail, etc.). More powerful but the boolean is cleaner for the if ($dispatch(...)) pattern and matches what dispatchEvent() natively returns.
Split into two separate PRs — The return value and the options parameter are independent features. The return value is zero-risk and universally useful. The options parameter adds API surface. Could merge the return value alone and debate the options separately.
Use a single options object — Instead of $dispatch(name, detail, options), combine into $dispatch(name, { detail, bubbles }). This would be a breaking change and less ergonomic for the common case where you just pass detail.
Changes Made
No changes made. The code is clean and follows Alpine conventions.
$dispatch with bubbles: false shouldn't bubble (parent listener) ✓
$dispatch with bubbles: false shouldn't bubble (same element listener) ✓
$dispatch cancelable via preventDefault() ✓
Regression verified: Reverted dispatch.js to main, rebuilt, ran tests — the bubbles-parent and cancelable tests both fail without the fix. CI also passes.
Code Review
packages/alpinejs/src/utils/dispatch.js:2-11 — The implementation is as minimal as it can be. Two lines changed. The ...options spread at the end correctly overrides the defaults.
packages/alpinejs/src/magics/$dispatch.js:4 — The existing .bind(dispatch, el) correctly passes through all additional arguments, so the third options param reaches the dispatch function as expected.
Potential footgun: If a user passes {detail: something} in the options object, it would override the detail positional parameter. This is unlikely in practice and is consistent with how spread overrides work, but worth noting.
Docs are included and well-written.
5 thumbs-up reactions on the PR show community demand.
Security
No security concerns identified. The options parameter only affects CustomEvent constructor options (bubbles, composed, cancelable) — no new attack surface.
Verdict
This is two features in one PR. Here's my take on each:
The return addition (returning dispatchEvent result): Merge this. It's zero-risk, backward-compatible (previously returned undefined, now returns a boolean), and enables useful conditional event patterns. This is the kind of "why wasn't it always like this?" improvement.
The options parameter: This is the one that needs your call, Caleb. It adds public API surface to $dispatch — a third parameter that most users will never need. The use case (preventing bubbling in nested same-name component events) is real, but the question is whether Alpine should solve it at the $dispatch level or if users should use unique event names / .self modifier / other existing patterns.
If you want to merge just the return value and skip the options parameter for now, I can strip it out. If you're comfortable with both, the implementation is clean and ready to go.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposal: Extend $dispatch functionality
Hello everyone,
While working with AlpineJS to build a UI library for Laravel, I noticed some limitations in the magic function $dispatch. Specifically:
These limitations can lead to issues when building modular, event-driven components. Below, I explain the motivation and propose a minimal enhancement.
Problem Overview
Currently, it's not possible to customize event behavior (e.g., disabling bubbles). This becomes problematic in cases like:
A modal and a dropdown both dispatching an event named
closed.When used together, the parent modal might unintentionally respond to the dropdown's event, causing conflicts.
Yes, we could rename events (e.g.,
modal:closedvs.dropdown:closed), but this doesn't solve issues with nested modals or more complex component structures.Although dispatched events are marked as
cancelable: true, the function does not return the created CustomEvent.As a result, it's not possible to check whether preventDefault() was called.
This prevents useful patterns like:
This kind of control is useful for building modular components.
Proposed Solution
The required change is minimal:
Benefits