Skip to main content Accessibility Feedback

Custom events in Web Components

The other day, I wrote about how to handle event listeners in Web Components. One other thing I love to bake into many of my Web Components are Custom Events.

Custom Events are events your library or Web Component emits that you can listen for with the addEventListener() method. They provide a way for developers to hook into your code and extend it in ways you might not anticipate, or do things it doesn’t support out-of-the-box.

They’re one of my favorite ways to make a Web Component more developer-friendly.

Let’s dig in!

An example

On a project I’m working on for NASA, I have a Web Component I use to asynchronously handle for submissions when JavaScript loads.

<ajax-form>
	<form method="POST" action="/path/to/backend">
		<!-- ... -->
	</form>
</ajax-form>

Inside that Web Component, I have another one that selectively disables <option> items in a <select> menu based on certain conditions.

<ajax-form>
	<form method="POST" action="/path/to/backend">
		<!-- ... -->
		<conditional-select deny='[...]'>
			<label for="planets">What' your favorite planet?</label>
			<select id="planets" name="planets">
				<option value="earth">Earth</option>
				<option value="mars">Mars</option>
				<option value="jupiter">Jupiter</option>
				<option value="saturn">Saturn</option>
			</select>
		</conditional-select>
	</form>
</ajax-form>

When my parent form submits, I want to remove the [disable] attribute from all of the <option> elements that have it inside my <conditional-select> element.

Custom events are a perfect use case for this! I can use them detect when the <ajax-form> successfully submits the form, then run more code in response.

🦄 Quick aside: if you want help on your website or app, I have one consulting spot left.

How to create a custom event

You can create a custom event with the new CustomEvent() constructor.

Pass in the name of the event as an argument. You can optionally pass in an object of options: whether or not the event bubbles, whether or not it’s canceable, and any detail you want shared with the event object.

The options.detail property can be any type of data, including an array or object.

// Create the event
let event = new CustomEvent('my-custom-event', {
	bubbles: true,
	cancelable: true,
	detail: 'This is awesome. I could also be an object or array.'
});

After creating your event, you pass it into the Element.dispatchEvent() method to emit it.

You can emit your event on any element. For Web Components, the component or custom element itself (this) is a good choice.

// Emit the event
this.dispatchEvent(event);

You can listen for custom events with the Element.addEventListener() method, just like browser-native events.

document.addEventListener('my-custom-event', function (event) {
	console.log(event.detail);
});

Naming conventions

To help prevent naming collisions, it’s a good idea to prefix custom events with your Web Component.

You can use kebab-case for this if you’d like.

let event = new CustomEvent('ajax-form-success', {});

I like to separate the library name and the action with a colon (:).

let event = new CustomEvent('ajax-form:success', {});

One thing to avoid is camelCase.

Event names are case-sensitive, and the property names on elements are case-insensitive and converted to lowercase by the browser. This can create conflicts if a developer uses on* listeners on elements.

Adding custom events to your Web Component

To make events easy to emit in my Web Components, I usually add an emit() method to my component class.

customElements.define('ajax-form', class extends HTMLElement {

	// Instantiate the component
	constructor () {
		super();
		// ...
	}

	emit () {
		// ...
	}

});

For the emit() method, I typically accept an event type and the detail to use as arguments, with the detail as optional.

/**
 * Emit a custom event
 * @param  {String} type   The event type
 * @param  {Object} detail Any details to pass along with the event
 */
emit (type, detail = {}) {

	// Create a new event
	let event = new CustomEvent(`ajax-form:${type}`, {
		bubbles: true,
		cancelable: true,
		detail: detail
	});

	// Dispatch the event
	return this.dispatchEvent(event);

}

Then, elsewhere in my Web Component, I can emit my event using this.emit().

this.emit('success', {
	data: new FormData(this.form)
	response
});

If you want to learn more about Web Components or Custom Events, I have a bunch of courses, tutorials, and projects over at the Lean Web Club.