# 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.
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.
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 Imperatively
Alternately, you can define both the listener and the handler in the owner component's JavaScript file.
<!-- owner.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.
// owner.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 and to any bubbling events from slotted content. It doesn't have access to events inside the shadow tree. See Pass Markup into Slots.
# Attach Event Listeners Declaratively and Dynamically
Note
Attaching event listeners using the lwc:on
directive is not available on the Salesforce Platform. See LWC Open Source.
To declaratively attach multiple event listeners, use the lwc:on
directive. Also, use this directive if you want to add event listeners whose event types are computed dynamically, for example, when passed from the owner component via @api
.
<template>
<button lwc:on={eventHandlers}>Click Me</button>
</template>
Pass an object to lwc:on
where each property key in the object specifies an event type, and the corresponding property's value is the event handler function. Property keys must be a string, such as click
, customEvent
, or 'custom-event'
.
import { LightningElement } from 'lwc';
export default class EventHandlerExample extends LightningElement {
eventHandlers = {
click: this.handleClick,
mouseover: this.handleMouseOver
};
handleClick(event) {
console.log('Button clicked');
}
handleMouseOver(event) {
console.log('Mouse over button');
}
}
An event handler that's added using lwc:on
is bound to the component instance; this
inside the handler refers to the component, allowing access to its properties and methods.
To dynamically add event listeners on a child component, define an object with event types and handlers in the owner component's JavaScript file. Then, use the lwc:on
directive in the owner component's template to bind these handlers to the child component.
In this example, the eventDynamicChild
component dispatches two custom events, customEvent
and anotherCustomEvent
, when it's connected to the DOM and when the button is clicked. The eventDynamic
component attaches the event listeners using lwc:on
.
The eventDynamic
component dynamically switches between two event listeners for customEvent
and anotherCustomEvent
. To toggle the message between the event listeners, click the Switch Event Listener button and then click the Update Message button.
# Dynamic Event Listeners Considerations
Consider these guidelines when using lwc:on
.
- Mutating the object referenced by the variable that's passed to
lwc:on
is not allowed. To modify a property on the object, pass a new object instead. Reusing the same object with modified properties throws an error. - Reassigning the variable that's passed to
lwc:on
to a new object is allowed.- If a property was in the previous object but ommited in the new object, then the corresponding event listener is removed.
- If a property was not in the previous object but present in the new object, then the corresponding event listener is added.
- If a property is present in both objects, then the corresponding event listener is updated.
- We recommend that you use the
lwc:on
directive instead oflwc:spread
for dynamically adding event listeners. - Using
lwc:on
and anonevent
listener for the same event type in the HTML template throws an error. - Using both
lwc:on
andlwc:spread
to specify listeners for the same event type results in both event listeners getting added to the element.
# Attach Event Listeners with Dynamic Component Loading
To dynamically load components and attach event listeners to them, use lwc:on
with lwc:component
. The lwc:component
element works together with the lwc:is
directive to load a child component dynamically.
<template>
<lwc:component lwc:is={childComponent} lwc:on={eventHandlers}></lwc:component>
</template>
Define your event handlers for the child component.
import { LightningElement } from 'lwc';
import ChildComponent from 'example/childComponent';
export default class ParentComponent extends LightningElement {
childComponent = ChildComponent;
eventHandlers = {
customEvent: this.handleCustomEvent
};
handleCustomEvent(event) {
console.log('Custom event received:', event.detail);
}
}
The child component contains a button that dispatches the custom event when clicked.
<template>
<button onclick={dispatchCustomEvent}>Click Me</button>
</template>
Dispatch the custom event in the child component, which is then handled by the parent component.
import { LightningElement } from 'lwc';
export default class ChildComponent extends LightningElement {
dispatchCustomEvent() {
const event = new CustomEvent('customEvent', {
detail: { message: 'Hello from Child Component!' }
});
this.dispatchEvent(event);
}
}
# 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.
If you add an event listener using lwc:on
, you could also remove the event listener by omitting that property in the object that's passed to lwc:on
.
# Create and Dispatch Events
Note
There are two things about web components that make creating and dispatching DOM events a little different: shadow DOM and slots. Before we dive into the details, make sure you're already familiar with shadow DOM and passing markup into slots.
When we use slots, although the content appears to be rendered inside the slot element, a “pointer” to the original content gets inserted into the slot. Understanding this concept helps you make sense of what’s happening with 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.
- No uppercase letters
- No spaces
- Use underscores to separate words
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 an example.
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 retargeted 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 information about an event, use the Event API.
-
event.target
— Gets a reference to the object that dispatched the event. As it bubbles up the tree, the value ofevent.target
changes to represent an element in the same scope as the listening element. This event retargeting preserves component encapsulation. We’ll see how this works later on. -
event.currentTarget
— Gets a reference to the element that the event handler is attached to. -
event.composedPath()
— Returns the event’s path, which is an array of the objects on which listeners will be invoked, depending on the configuration used.
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
.
# Static Composition
A static composition doesn't use slots. In this simple example, c-app
composes c-parent
, which in turn composes c-child
.
<!-- body -->
<c-app onbuttonclick={handleButtonClick}></c-app>
<!-- app -->
<template>
<h2>My app</h2>
<c-parent onbuttonclick={handleButtonClick}></c-parent>
</template>
<!-- parent.html -->
<template>
<h3>I'm a parent component</h3>
<div class='wrapper' onbuttonclick={handleButtonClick}>
<c-child onbuttonclick={handleButtonClick}></c-child>
</div>
</template>
<!-- child.html -->
<template>
<h3>I'm a child component</h3>
<button onclick={handleClick}>click me</button>
</template>
// child.js
handleClick() {
const buttonclicked = new CustomEvent('buttonclick', {
//event options
});
this.dispatchEvent(buttonclicked);
}
We fire an event, buttonclick
, from c-child
when the button
is clicked. We attached event listeners for the custom event on the following elements:
body
c-app
hostc-parent
hostdiv.wrapper
c-child
host
The flattened tree looks like this:
<body> <!-- Listening for buttonclick event -->
<c-app> <!-- Listening for buttonclick event -->
#shadow-root
| <h2>My app</h2>
| <c-parent> <!-- Listening for buttonclick event -->
| #shadow-root
| | <h3>I'm a parent component</h3>
| | <div class="wrapper"> <!-- Listening for buttonclick event -->
| | <c-child> <!-- Listening for buttonclick event -->
| | #shadow-root
| | | <h3>I'm a child component</h3>
| | | <button>click me</button>
| | </c-child>
| | </div>
| </c-parent>
</c-app>
</body>
# bubbles: false
and composed: false
This default configuration is recommended because it’s the least disruptive and provides the best encapsulation for your component.
With this configuration, only c-child
gets to react to the buttonclick
event fired from c-child
. The event doesn’t bubble past the host. The only way to listen to this event is to add an event listener directly on the component that dispatches the event.
This is where you start, then from here, you can start incorporating other more permissive configurations, like the ones we’re about to explore in the next few sections, to fit your requirements.
<body>
<c-app>
#shadow-root
| <c-parent>
| #shadow-root
| | <div class="wrapper">
| | <c-child> <!-- Event flows up to here -->
| | #shadow-root
| | | <h3>I'm a child component</h3>
| | | <button>click me</button>
| | </c-child>
| | </div>
| </c-parent>
</c-app>
</body>
Inspecting c-child
handlers return these values on the event.
event.currentTarget
=c-child
event.target
=c-child
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 until it finds a shadow root or the event gets canceled. It doesn’t cross the shadow boundary.
As a result, both c-child
and div.wrapper
can react to the event.
<body>
<c-app>
#shadow-root
| <c-parent>
| #shadow-root
| | <div class="wrapper"> <!-- Event flows up to here -->
| | <c-child> <!-- Event flows up to here -->
| | #shadow-root
| | | <h3>I'm a child component</h3>
| | | <button>click me</button>
| | </c-child>
| | </div>
| </c-parent>
</c-app>
</body>
The event handlers return the following.
c-child
handler
event.currentTarget
=c-child
event.target
=c-child
div.childWrapper
handler:
event.currentTarget
=div.childWrapper
event.target
=c-child
There are two ways to bubble up an event:
- Bubble an event up inside the component’s template. This technique creates an internal event.
- Bubble an event up inside the containing component’s template. Use this technique to send an event to a component's grandparent when the component is passed into a slot. Note that this technique works only with the LWC synthetic shadow DOM. With native shadow DOM, the event doesn't pass out of the slot unless
composed
is alsotrue
.
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’s 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.
Example
The eventBubbling
component in the lwc-recipes-oss repo consumes a contactListItemBubbling
component, which creates an event with bubbles: true
and composed: false
. This recipe uses slots and sends an event to a grandparent. Note that this technique works only with the LWC synthetic shadow DOM. With native shadow DOM, the event doesn't pass out of the slot unless composed
is also true
.
# bubbles: false
and composed: true
This configuration is an anti-pattern and it's not supported in LWC synthetic shadow DOM, but it’s helpful for understanding how events bubble in a shadow DOM context.
Composed events can break shadow boundaries and bounce from host to host along their path. They don’t continue to bubble beyond that, unless they also set bubbles: true
.
<body>
<c-app> <!-- Event flows up to here -->
#shadow-root
| <c-parent> <!-- Event flows up to here -->
| #shadow-root
| | <div class="wrapper">
| | <c-child> <!-- Event flows up to here -->
| | #shadow-root
| | | <h3>I'm a child component</h3>
| | | <button>click me</button>
| | </c-child>
| | </div>
| </c-parent>
</c-app>
</body>
In this case, c-child
, c-parent
, and c-app
can react to the event. It’s interesting to note that div.wrapper
can’t handle the event, because the event doesn't bubble in the shadow itself.
Let's see what the handlers return for the event.
c-child
handler:
event.currentTarget
=c-child
event.target
=c-child
c-parent
handler:
event.currentTarget
=c-parent
event.currentTarget
=c-parent
c-app
handler:
event.currentTarget
=c-app
event.target
=c-app
Even though c-child
fired the event, when it gets to c-parent
and c-app
, the event shows the host as both target
and currentTarget
.
With event retargeting, as the event leaves c-child
's shadow, the event gets treated as an implementation detail and its target is changed to match the scope of the listener.
Note
Use the composed:true
flag with caution. We recommend that you repack and resend the event so that your receivers are able to understand the event. For example, c-parent
receives the event from c-child
and exposes a new custom event, so the elements on c-container
's light tree has the right context.
# bubbles: true
and composed: true
This configuration is an anti-pattern because it creates an event that crosses every boundary. Every element gets the event, even the regular DOM elements that aren’t part of any shadow. The event can bubble all the way up to the document element.
The event bubbles up through the DOM, crosses the shadow boundary, and continues bubbling up through the DOM to the document root.
When firing events this way, you can pollute the event space, leak information and create confusing semantics. Events are considered part of your component’s API, so make sure that anyone on the event path is able to understand and handle the event’s payload if it has one.
<body> <!-- Event flows up to here -->
<c-app> <!-- Event flows up to here -->
#shadow-root
<c-parent> <!-- Event flows up to here -->
#shadow-root
<div class="wrapper"> <!-- Event flows up to here -->
<c-child> <!-- Event flows up to here -->
#shadow-root
</c-child>
</div>
</c-parent>
</c-app>
</body>
Important
Whenever composed
is set to true
, 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
.