Custom Events
Objectives
By the end of this chapter, you should be able to:
- Create your own event with
CustomEvent - Attach data to a custom event with
detail - Dispatch a custom event with
.dispatchEvent(), and listen for it like any built-in event
💡 Why this matters: Built-in events (
click,submit,keydown) cover what a user does. Custom events let different pieces of your own code talk to each other the same way, useful once a page gets bigger than one script.
Creating and Dispatching a Custom Event
new CustomEvent(name, options) creates an event with any name you choose. .dispatchEvent() fires it on an element, exactly like a real browser event.
Given this HTML:
<div id="cart"></div>
const cart = document.getElementById("cart");
cart.addEventListener("item-added", (event) => {
console.log("Item added event received");
});
const event = new CustomEvent("item-added");
cart.dispatchEvent(event);
// Item added event received
The listener doesn’t know or care that "item-added" isn’t a built-in event name, .addEventListener() works identically for custom and built-in events.
Passing Data with detail
A custom event is far more useful with data attached. The detail option holds whatever value you want, an object is common.
cart.addEventListener("item-added", (event) => {
console.log(`Added ${event.detail.name} ($${event.detail.price})`);
});
const event = new CustomEvent("item-added", {
detail: { name: "Eggs", price: 5 },
});
cart.dispatchEvent(event);
// Added Eggs ($5)
event.detail carries the object through to the listener, unchanged. This is how one part of your code (something that just added an item to a cart) can hand information to another part (something that updates a total, or shows a confirmation message) without those two parts needing to call each other’s functions directly.
Custom Events Bubble Too, If You Let Them
By default, a custom event does not bubble. Pass bubbles: true if you want it to, the same as any other event.
const event = new CustomEvent("item-added", {
detail: { name: "Eggs", price: 5 },
bubbles: true,
});
cart.dispatchEvent(event);
With bubbles: true, a listener attached to cart’s parent, or even document, would also catch this event. Without it, only a listener attached directly to cart receives it.
Try It
Starter HTML for all three exercises:
<div id="app">
<div id="child"></div>
</div>
- Attach a listener for a custom event named
"ready"to#app, dispatch it, and confirm the listener logs a message. - Attach a listener for a
"score-changed"event to#app, dispatch it withdetail: { score: 10 }, and logevent.detail.scorefrom the listener. - Attach a listener for
"item-removed"to#app, dispatch it from#childwithbubbles: true, and confirm#app’s listener still fires.
Recap
new CustomEvent(name, options)creates an event with any name..dispatchEvent()fires it, and.addEventListener()listens for it exactly like a built-in event.detailattaches any data you want to the event, read on the listener side asevent.detail.- Custom events don’t bubble by default. Pass
bubbles: trueif a listener on an ancestor also needs to catch it.
Next lesson: this module’s exercises, then the mini project, a working to-do list built entirely from the DOM and events.