Skip to content

Custom Events

Custom elements communicate outward by dispatching events. The browser’s built-in CustomEvent constructor lets you attach an arbitrary payload — the detail object — so the parent can receive structured data without ever reaching inside the element. This page covers how to create, dispatch, and listen to custom events.

A CustomEvent is created with a type string and an options object. The most important option is detail, which carries your payload:

var event = new CustomEvent('count-change', {
detail: { count: 42 },
bubbles: true,
});

bubbles: true makes the event travel up through the DOM tree, so a parent or ancestor — not just the element itself — can listen for it. Without this flag the event only fires on the element it was dispatched from and goes no further.

Call this.dispatchEvent(event) from anywhere inside your class to fire the event:

class MyCounter extends HTMLElement {
connectedCallback() {
this._count = 0;
this.innerHTML = '<button>Count: 0</button>';
this.querySelector('button').addEventListener('click', () => {
this._count++;
this.querySelector('button').textContent = 'Count: ' + this._count;
this.dispatchEvent(new CustomEvent('count-change', {
detail: { count: this._count },
bubbles: true,
}));
});
}
}
customElements.define('my-counter', MyCounter);

The element does not care who is listening. It just announces that something happened and provides the data. This is the key to keeping elements reusable.

Any code that has a reference to the element — or any ancestor, because bubbles: true — can register a listener with the standard addEventListener API:

// Parent listening
document.querySelector('my-counter').addEventListener('count-change', (e) => {
console.log('Count is now', e.detail.count);
});

You can also listen at document level when bubbles: true is set, which is convenient when you do not have a direct reference to the element.

A callback-prop pattern — passing a function reference as a property — works in JavaScript, but it creates a tight dependency: the element must know that the consumer expects a function on a specific property name, and the consumer must ensure the function is set before the element uses it.

Events are loosely coupled by design. Any number of listeners can subscribe or unsubscribe at any time without the element knowing. The element’s public API stays predictable: it dispatches a named event with a documented detail shape, and nothing more.

The click-counter element below tracks how many times its button has been clicked. Each click dispatches a counter-change event with { value: N } in the detail. The page listens at document level and shows the latest value in a log area.

What property of `CustomEvent` carries the event payload?
Which option makes a CustomEvent travel up the DOM tree?
How do you dispatch a custom event from inside an element?