Custom Events
This lesson explains Custom Events with clear examples, use cases, and best practices so you can build reusable Web Components confidently.
What Are Custom Events?
Custom Events allow Web Components to communicate with the outside page,
parent components, or application JavaScript by dispatching their own
events.
Instead of exposing internal methods or DOM structure, a Web Component can
send meaningful events like user-selected,
cart-updated, or modal-closed.
| Concept | Description |
CustomEvent | JavaScript API used to create custom browser events |
dispatchEvent() | Sends the event from the component |
detail | Object used to pass custom data with the event |
bubbles | Allows the event to bubble up through the DOM |
composed | Allows the event to cross the Shadow DOM boundary |
| Main Benefit | Clean communication from Web Components to application code |
Basic Custom Event Example
const event = new CustomEvent("user-selected", {
detail: {
id: 101,
name: "John"
}
});
element.dispatchEvent(event);
The event name is user-selected, and custom data is passed
through the detail object.
Custom Events in Web Components
class AppButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<button type="button">
<slot>Click Me</slot>
</button>
`;
}
connectedCallback() {
const button = this.shadowRoot.querySelector("button");
button.addEventListener("click", () => {
this.dispatchEvent(new CustomEvent("app-click", {
detail: {
message: "Button clicked"
},
bubbles: true,
composed: true
}));
});
}
}
customElements.define("app-button", AppButton);
<app-button>Save</app-button>
<script>
const button = document.querySelector("app-button");
button.addEventListener("app-click", event => {
console.log(event.detail.message);
});
</script>
The component dispatches an app-click event when the internal
button is clicked. The outside page listens to the event without accessing
the internal Shadow DOM.
How Custom Events Work
| Step | What Happens |
| 1. Create event | Use new CustomEvent("event-name") |
| 2. Add data | Pass custom data using detail |
| 3. Dispatch event | Send it with dispatchEvent() |
| 4. Enable bubbling | Use bubbles: true when parent elements should hear it |
| 5. Cross Shadow DOM | Use composed: true when the event must leave Shadow DOM |
| 6. Listen outside | Use addEventListener() on the component or parent |
Passing Data With detail
this.dispatchEvent(new CustomEvent("product-added", {
detail: {
id: "p101",
name: "Wireless Mouse",
price: 29.99,
quantity: 1
},
bubbles: true,
composed: true
}));
document.addEventListener("product-added", event => {
console.log(event.detail.name);
console.log(event.detail.quantity);
});
Use detail to pass useful information from the component to
the outside application.
bubbles and composed
| Option | Meaning | Common Value |
bubbles | Allows the event to move up through parent elements | true |
composed | Allows the event to cross Shadow DOM boundary | true |
cancelable | Allows listeners to call preventDefault() | true when action can be canceled |
this.dispatchEvent(new CustomEvent("modal-close", {
detail: {
reason: "escape-key"
},
bubbles: true,
composed: true,
cancelable: true
}));
For most Web Component communication, use bubbles: true and
composed: true so parent components can listen to the event.
Cancelable Custom Events
const event = new CustomEvent("before-delete", {
detail: {
id: 10
},
bubbles: true,
composed: true,
cancelable: true
});
const allowed = this.dispatchEvent(event);
if (!allowed) {
console.log("Delete action was canceled");
return;
}
this.deleteItem();
document.addEventListener("before-delete", event => {
const confirmDelete = window.confirm("Delete this item?");
if (!confirmDelete) {
event.preventDefault();
}
});
Cancelable events allow outside code to stop an action before the component
continues.
Custom Event Naming Patterns
| Pattern | Example | Use |
| Past action | item-selected | After something already happened |
| Before action | before-delete | Before an action that can be canceled |
| State change | value-changed | When component state changes |
| Component prefix | app-modal-close | Avoid naming conflicts in large apps |
| Kebab case | cart-item-added | Recommended naming style for custom events |
Listening From a Parent Element
<section id="cart">
<product-card product-id="101"></product-card>
</section>
<script>
const cart = document.querySelector("#cart");
cart.addEventListener("product-added", event => {
console.log("Product added:", event.detail);
});
</script>
Because the event bubbles, the parent element can listen for events from
child Web Components.
Custom Events and Shadow DOM
this.shadowRoot.querySelector("button").addEventListener("click", () => {
this.dispatchEvent(new CustomEvent("user-action", {
detail: {
action: "save"
},
bubbles: true,
composed: true
}));
});
When using Shadow DOM, composed: true is important because it
allows the event to leave the component and reach external listeners.
Custom Events vs Callback Properties
| Feature | Custom Events | Callback Properties |
| HTML Friendly | Yes, works naturally with DOM events | No, requires JavaScript property assignment |
| Multiple Listeners | Supports many listeners | Usually one callback |
| Parent Communication | Good for parent and app-level communication | Good for direct JavaScript control |
| Shadow DOM | Works with composed: true | Does not bubble through DOM |
| Recommended For | Reusable Web Component APIs | Special advanced use cases |
When to Use Custom Events
- You want a Web Component to notify the outside page about actions.
- You need parent components to listen for child component changes.
- You want to avoid exposing internal Shadow DOM structure.
- You are building reusable design system components.
- You need multiple listeners for the same component event.
- You want framework-independent communication.
Common Custom Event Use Cases
- Button clicked:
app-click - Modal closed:
modal-closed - Tab selected:
tab-selected - Form submitted:
form-submitted - Input value changed:
value-changed - Product added to cart:
product-added
Custom Events Best Practices
- Use clear event names like
item-selected or value-changed. - Use kebab-case for custom event names.
- Pass useful data through
detail. - Use
bubbles: true for parent-level communication. - Use
composed: true when events must cross Shadow DOM. - Use
cancelable: true only when outside code can stop the action. - Document event names and
detail payload structure.
Common Custom Event Mistakes
- Forgetting to use
composed: true inside Shadow DOM. - Forgetting
bubbles: true when parent listeners are needed. - Using unclear event names like
change or click2. - Putting too much unrelated data inside
detail. - Dispatching events before the component is ready.
- Using callbacks when native DOM events would be cleaner.
- Not documenting custom events for component consumers.
Key Takeaways
- Custom Events help Web Components communicate with the outside page.
- Use
new CustomEvent() to create a custom event. - Use
detail to pass custom data. - Use
bubbles: true for parent listeners. - Use
composed: true when working with Shadow DOM. - Custom Events are a clean public API for reusable components.
Pro Tip
For Web Components with Shadow DOM, most public events should use
bubbles: true and composed: true so application
code can listen from parent elements without touching internal markup.