You can create your own events and dispatch them on any element. Other parts of your code listen for them with addEventListener, just like click events. This lets components talk to each other without knowing about each other.
<div id="cart"></div>
<button id="add">Add to cart</button>
<script>
const cart = document.querySelector("#cart");
// a listener, somewhere in the code
document.addEventListener("cart:add", event => {
console.log("Item added:", event.detail.name);
cart.textContent = "Last added: " + event.detail.name;
});
// somewhere else: announce what happened
document.querySelector("#add").addEventListener("click", () => {
document.dispatchEvent(new CustomEvent("cart:add", { detail: { name: "Notebook", price: 4 } }));
});
</script>
The parts
new CustomEvent(name, options)creates the event.- The
detailoption carries any data you want. It appears asevent.detail. element.dispatchEvent(event)fires it.
Bubbling
Custom events do not bubble by default. Add bubbles: true if parents should hear them.
<section id="parent">
<button id="child">Select</button>
</section>
<script>
document.querySelector("#parent").addEventListener("selected", event => {
console.log("parent heard:", event.detail);
});
document.querySelector("#child").addEventListener("click", event => {
event.currentTarget.dispatchEvent(new CustomEvent("selected", { detail: "item 7", bubbles: true }));
});
</script>
Why use them
- Decoupling. A “cart” can announce
cart:addand anything can react, such as a badge, a mini cart, analytics, without the cart code knowing about them. - Reusable components. A tabs component can fire
tab:change. - Matches how built-in events work, so other developers find it familiar.
Naming
Use clear names, often with a prefix to avoid clashes: cart:add, modal:open.
Cancelable events
If you create the event with cancelable: true, listeners can call preventDefault(), and dispatchEvent returns false if they did.
const ok = document.dispatchEvent(new CustomEvent("before-save", { cancelable: true }));
console.log(ok); // → true (nobody cancelled it)
The simple version: an EventTarget
You do not need a DOM element. Any EventTarget works:
const bus = new EventTarget();
bus.addEventListener("ping", event => console.log("got", event.detail));
bus.dispatchEvent(new CustomEvent("ping", { detail: 42 }));