Events

Lightning web components dispatch standard DOM events. Components can also create and dispatch custom events.

Use events to communicate up the component containment hierarchy. For example, a child component, example-todo-item, dispatches an event to tell its parent, example-todo-app, that a user selected it.

Events in Lightning web components are built on DOM Events, a collection of APIs and objects available in every browser.

Lightning web components implement the EventTarget interface, which allows them to dispatch events, listen for events, and handle events.

To create events, we strongly recommend using the CustomEvent interface instead of the Event interface. In Lightning web components, CustomEvent provides a more consistent experience across browsers, including Internet Explorer.

Tip

To communicate up the component hierarchy, use events. To communicate down the component hierarchy, pass properties to a child via HTML attributes, or call its public methods.

Handle Events

There are two ways to listen for an event: declaratively from the component’s HTML template, or programmatically using an imperative JavaScript API. It’s better to listen from the HTML template since it reduces the amount of code you need to write.

To handle an event, define a method in the component’s JavaScript class.

Attach Event Listeners Declaratively

This example uses two components in the example namespace, <example-owner> and <example-child>.

The child component has an HTML button, which emits a standard click event.

<!-- child.html -->
<template>
    <button>Click Me</button>
</template>

To listen for an event, a component uses an HTML attribute with the syntax oneventtype. In the template of the owner component, to listen for the click event emitted from <example-child>, declare the listener onclick.

<!-- owner.html -->
<template>
    <example-child onclick={handleClick}></example-child>
</template>

In the JavaScript class of the owner component, define the handleClick method, which executes when the click event fires.

// owner.js
import { LightningElement } from 'lwc';

export default class Owner extends LightningElement {
    handleClick(e){
        // Your code here
    }
}

Attach Event Listeners in JavaScript

Alternately, you can define both the listener and the handler in the owner component's JavaScript file.

<!-- parent.html -->
<template>
    <example-child></example-child>
</template>
<!-- child.html -->
<template>
    <button>Click</button>
</template>

Get a reference to <example-child> using this.template.querySelector. To handle the event, define handleClick in the JavaScript file of the owner.

// parent.js
import { LightningElement } from 'lwc';

export default class App extends LightningElement {
    renderedCallback(){
        this.template.querySelector('example-child').addEventListener('click',
         this.handleClick);
    }

    handleClick(e){
        // Your code here
    }
}

There are two syntaxes for adding an event listener. One adds an event listener to an element within a component’s shadow boundary. One adds an event listener to an element that the template doesn’t own, for example, an element passed into a slot.

To add an event listener to an element within the shadow boundary, use template.

this.template.addEventListener()

In the previous example, the parent.js code uses this.template syntax to select example-child because example-child is within its shadow boundary. An event listener added via this.template.addEventListener has access to bubbling events inside the shadow tree.

To add an event listener to an element that a template doesn’t own, call addEventListener directly.

this.addEventListener()

An event listener added via this.addEventListener binds to the host element and has access to events on the host element as well as to any bubbling events from slotted content. It doesn't have access to events inside the shadow tree. See Pass Markup into Slots.

Remove Event Listeners

As part of the component lifecycle, the browser manages and cleans up listeners, so you don’t have to worry about it.

However, if you add a listener to the global window object, you’re responsible for removing the listener yourself within the appropriate lifecycle hook. In this case, use the connectedCallback and disconnectedCallback methods to add and remove the event listeners.

Create and Dispatch Events

Create and dispatch events in a component’s JavaScript class. To create an event, use the CustomEvent() constructor. To dispatch an event, call the EventTarget.dispatchEvent() method.

The CustomEvent() constructor has one required parameter, which is a string indicating the event type. As a component author, you name the event type when you create the event. The event type is the name of the event. You can use any string as your event type. However, we recommend that you conform with the DOM event standard.

Don’t prefix your event name with the string on, because inline event handler names must start with the string on. If your event is called onmessage, the markup would be <example-my-component ononmessage={handleMessage}>. Notice the doubled word onon, which is confusing.

Tip

To learn more about events, read the MDN Introduction to events and see this list of standard Event types.

Let’s jump into some code.

The paginator component contains Previous and Next buttons. When a user clicks a button, the handlePrevious or handleNext function executes. These functions create and dispatch the previous and next events. You can drop the paginator component into any component that needs Previous and Next buttons. That component listens for the events and handles them.

These events are simple something happened events. They don’t pass a data payload up the DOM tree, they simply announce that a user clicked a button.

Let’s drop paginator into a component called eventSimple, which listens for and handles the previous and next events.

In the playground, click the eventSimple.html tab. To listen for events, use an HTML attribute with the syntax oneventtype. Since our event types are previous and next, the listeners are onprevious and onnext.

Click the eventSimple.js tab. When eventSimple receives the previous and next events, the handlePrevious and handleNext functions increase and decrease the page number.

Pass Data in Events

To communicate data to elements in the same shadow tree, the consumer of the event can access the property off event.target, which is a reference to the object that dispatched the event. For example, a component could access event.target.myProperty. In this case, don't add the property to event.detail.

To communicate data to elements that aren’t in the same shadow tree, use event.detail. In these cases, event.target.* doesn’t work because the real target isn’t visible to the listener. (When an event bubbles up the DOM, if it crosses the shadow boundary, the value of Event.target changes to match the scope of the listener. The event is re-targeted so the listener can’t see into the shadow tree of the component that dispatched the event.)

Receiving components access the data in the detail property in the event listener’s handler function.

Important

The CustomEvent interface imposes no type requirements or structure on the detail property. However it’s important to send only primitive data. JavaScript passes all data types by reference except for primitives. If a component includes an object in its detail property, any listener can mutate that object without the component’s knowledge. This is a bad thing! It’s a best practice either to send only primitives, or to copy data to a new object before adding it to the detail property. Copying the data to a new object ensures that you’re sending only the data you want, and that the receiver can’t mutate your data.

Example

Check out the eventWithDetail component in the lwc-recipes-oss repo.

Get an Event Target

To get a reference to the object that dispatched the event, use the Event.target property, which is part of the DOM API for events.

Example

Several recipes in the lwc-recipes-oss repo use Event.target. To find examples, search the repo for event.target.

When an event bubbles up the DOM, if it crosses the shadow boundary, the value of Event.target changes to match the scope of the listener. This change is called “event retargeting.” The event is retargeted so the listener can’t see into the shadow DOM of the component that dispatched the event. Event retargeting preserves shadow DOM encapsulation.

Let’s look at a simple example.

<!-- myButton.html -->
<template>
    <button>{label}</button>
</template>

A click listener on <my-button> always receives my-button as the target, even if the click happened on the button element.

Imagine an event is dispatched from a div element in the example-todo-item component. Within the component’s shadow DOM, Event.target is div. But to a listener on the p element in the containing example-todo-app component, the Event.target is example-todo-item, because the p element can’t see into the example-todo-item shadow DOM.

<example-todo-app>
  #shadow-root
    <div>
        <p>Your To Do List</p>
    </div>
    <example-todo-item>
      #shadow-root
        <div>
            <p>Go to the store</p>
        </div>
    </example-todo-item>
</example-todo-app>

It’s interesting to note that to a listener on example-todo-item, the Event.target is example-todo-item, not div, because example-todo-item is outside the shadow boundary.

Configure Event Propagation

After an event is fired, it can propagate up through the DOM. To understand where events can be handled, understand how they propagate. Lightning web component events propagate according to the same rules as DOM events. When you create an event, define event propagation behavior using two properties on the event, bubbles and composed.

bubbles A Boolean value indicating whether the event bubbles up through the DOM or not. Defaults to false.

composed A Boolean value indicating whether the event can pass through the shadow boundary. Defaults to false.

bubbles: false and composed: false

The default configuration. The event doesn’t bubble up through the DOM and doesn’t cross the shadow boundary. The only way to listen to this event is to add an event listener directly on the component that dispatches the event.

This configuration is recommended because it’s the least disruptive.

In this example, example-my-component creates and dispatches a simple event called notify.

// myComponent.js
import { LightningElement } from 'lwc';

export default class MyComponent extends LightningElement {
    connectedCallback(){
        this.dispatchEvent(
            // Default values for bubbles and composed are false.
            new CustomEvent('notify')
        );
    }
}

example-my-component is nested in example-app. The example-app template adds an event listener on example-my-component to listen for the notify event.

<!-- app.html -->
<template>
    <!-- Handlers on the component execute. -->
    <example-my-component onnotify={handleNotify}></example-my-component>
</template>

When example-app hears the event, its handleNotify() function executes.

// app.js
import { LightningElement } from 'lwc';

export default class App extends LightningElement {
    handleNotify() {
        console.log('handleNotify executed');
    }
}

If you run this code in the Playground, you see this output in the Console.

handleNotify executed

Example

The eventWithData component in the lwc-recipes-oss repo consumes a contactListItem component, which creates an event with bubbles: false and composed: false.

bubbles: true and composed: false

The event bubbles up through the DOM, but doesn’t cross the shadow boundary.

There are two ways to bubble up an event:

To bubble an event inside the component’s template, dispatch the event on an element in the template. The event bubbles up to the element’s ancestors inside the template only. When the event reaches the shadow boundary, it stops.

// myComponent.js
this.template.querySelector('div')
    .dispatchEvent(
        new CustomEvent('notify', { bubbles: true })
);

The event must be handled in myComponent.js. Handlers in the containing component don’t execute because the event doesn’t cross the shadow boundary.

<!-- owner.html -->
    <!-- handleNotify doesn’t execute -->
    <my-component onnotify={handleNotify}></my-component>
</div>

We don’t recommend bubbling events outside of your own template, but it is possible. To bubble an event to the template that contains your component, dispatch the event on the host element. The event is visible only in the template that contains your component.

// myComponent.js
this.dispatchEvent(
    new CustomEvent('notify', { bubbles: true })
);
<!-- owner.html -->
<!-- handleNotifyOuter can handle the event -->
<div onnotify={handleNotifyOuter}>
    <!-- handleNotifyInner can handle the event -->
    <my-component onnotify={handleNotifyInner}></my-component>
</div>

Example

The eventBubbling component in the lwc-recipes-oss repo consumes a contactListItemBubbling component, which creates an event with bubbles: true and composed: false.

bubbles: true and composed: true

The event bubbles up through the DOM, crosses the shadow boundary, and continues bubbling up through the DOM to the document root.

Important

If an event uses this configuration, the event type becomes part of the component’s public API. It also forces the consuming component and all of its ancestors to include the event as part of their APIs.

Because this configuration bubbles your event all the way to the document root, it can cause name collisions. Name collisions can cause the wrong event listeners to fire. You may want to prefix your event type with a namespace, like mydomain__myevent. The HTML event listener would have the awkward name onmydomain__myevent.

Dispatching events or adding listeners to the capture phase isn't supported. Simply think of the event’s path as starting with your component, and then moving to its parent, and then grandparent, and so on.

bubbles: false and composed: true

Lightning web components don’t use this configuration.