Events are the standard way that elements communicate changes. These changes typically occur due to user interaction. For example, a button dispatches a click event when a user clicks on it; an input dispatches a change event when the user enters a value in it.
In addition to these standard events that are automatically dispatched, Lit elements can dispatch custom events. For example, a menu element might dispatch an event to indicate the selected item changed; a popup element might dispatch an event when the popup opens or closes.
Listening to eventsPermalink to “Listening to events”
In addition to the standard
addEventListener API, Lit introduces a declarative way to add event listeners.
Adding event listeners in the element templatePermalink to “Adding event listeners in the element template”
You can use
@ expressions in your template to add event listeners to element's in your component template. Declarative event listeners are added when the template is rendered.
Customizing event listener options
If you need to customize the event options used for a declarative event listener (like
capture), you can specify these on the listener using the
@eventOptions decorator. The object passed to
@eventOptions is passed as the
options parameter to
Adding event listeners to the component or its shadow rootPermalink to “Adding event listeners to the component or its shadow root”
To be notified of an event dispatched from the component's slotted children as well as children rendered into shadow DOM via the component template, you can add a listener to the component itself using the standard
addEventListener DOM method. See EventTarget.addEventListener() on MDN for full details.
The component constructor is a good place to add event listeners on the component.
Adding event listeners to the component itself is a form of event delegation and can be done to reduce code or improve performance. See event delegation for details. Typically when this is done, the event's
target property is used to take action based on which element fired the event.
However, events fired from the component's shadow DOM are retargeted when heard by an event listener on the component. This means the event target is the component itself. See Working with events in shadow DOM for more information.
Retargeting can interfere with event delegation, and to avoid it, event listeners can be added to the component's shadow root itself. Since the
shadowRoot is not available in the
constructor, event listeners can added in the
createRenderRoot method as follows. Please note that it's important to make sure to return the shadow root from the
Adding event listeners to other elementsPermalink to “Adding event listeners to other elements”
If your component adds an event listener to anything except itself or its templated DOM – for example, to
Document, or some element in the main DOM – you should add the listener in
connectedCallback and remove it in
Removing the event listener in
disconnectedCallbackensures that any memory allocated by your component will be cleaned up when your component is destroyed or disconnected from the page.
Adding the event listener in
connectedCallback(instead of, for example, the constructor or
firstUpdated) ensures that your component will re-create its event listener if it is disconnected and subsequently reconnected to DOM.
See the MDN documentation on using custom elements lifecycle callbacks for more information on
Optimizing for performancePermalink to “Optimizing for performance”
Adding event listeners is extremely fast and typically not a performance concern. However, for components that are used in high frequency and need a lot of event listeners, you can optimize first render performance by reducing the number of listeners used via event delegation and adding listeners asynchronously after rendering.
Using event delegation can reduce the number of event listeners used and therefore improve performance. It is also sometimes convenient to centralize event handling to reduce code. Event delegation can only be use to handle events that
bubble. See Dispatching events for details on bubbling.
Bubbling events can be heard on any ancestor element in the DOM. You can take advantage of this by adding a single event listener on an ancestor component to be notified of a bubbling event dispatched by any of its descendants in the DOM. Use the event's
target property to take specific action based on the element that dispatched the event.
Asynchronously adding event listeners
To add an event listener after rendering, use the
firstUpdated method. This is a Lit lifecycle callback which runs after the component first updates and renders its templated DOM.
firstUpdated callback fires after the first time your component has been updated and called its
render method, but before the browser has had a chance to paint.
See firstUpdated in the Lifecycle documentation for more information.
To ensure the listener is added after the user can see the component, you can await a Promise that resolves after the browser paints.
Understanding Permalink to “Understanding this in event listeners”
this in event listeners
Event listeners added using the declarative
@ syntax in the template are automatically bound to the component.
Therefore, you can use
this to refer to your component instance inside any declarative event handler:
When adding listeners imperatively with
addEventListener, you'll want to use an arrow function so that
this refers to the component:
See the documentation for
this on MDN for more information.
Listening to events fired from repeated templatesPermalink to “Listening to events fired from repeated templates”
When listening to events on repeated items, it's often convenient to use event delegation if the event bubbles. When an event does not bubble, a listener can be added on the repeated elements. Here's an example of both methods:
Dispatching eventsPermalink to “Dispatching events”
All DOM nodes can dispatch events using the
dispatchEvent method. First, create an event instance, specifying the event type and options. Then pass it to
dispatchEvent as follows:
bubbles option allows the event to flow up the DOM tree to ancestors of the dispatching element. It's important to set this flag if you want the event to be able to participate in event delegation.
composed option is useful to set to allow the event to be dispatched above the shadow DOM tree in which the element exists.
See Working with events in shadow DOM for more information.
See EventTarget.dispatchEvent() on MDN for a full description of dispatching events.
When to dispatch an eventPermalink to “When to dispatch an event”
Events should be dispatched in response to user interaction or asynchronous changes in the component's state. They should generally not be dispatched in response to state changes made by the owner of the component via its property or attribute APIs. This is generally how native web platform elements work.
For example, when a user types a value into an
input element a
change event is dispatched, but if code sets the
value property, a
change event is not dispatched.
Similarly, a menu component should dispatch an event when the user selects a menu item, but it should not dispatch an event if, for example, the menu's
selectedItem property is set.
This typically means that a component should dispatch an event in response to another event to which it is listening.
Dispatching events after an element updatesPermalink to “Dispatching events after an element updates”
Often, an event should be fired only after an element updates and renders. This might be necessary if an event is intended to communicate a change in rendered state based on user interaction. In this case, the component's
updateComplete Promise can be awaited after changing state, but before dispatching the event.
Using standard or custom eventsPermalink to “Using standard or custom events”
Events can be dispatched either by constructing an
Event or a
CustomEvent. Either is a reasonable approach. When using a
CustomEvent, any event data is passed in the event's
detail property. When using an
Event, an event subclass can be made and custom API attached to it.
See Event on MDN for details about constructing events.
Firing a custom event:
See the MDN documentation on custom events for more information.
Firing a standard event:
Working with events in shadow DOMPermalink to “Working with events in shadow DOM”
When using shadow DOM there are a few modifications to the standard event system that are important to understand. Shadow DOM exists primarily to provide a scoping mechanism in the DOM that encapsulates details about these "shadow" elements. As such, events in shadow DOM encapsulate certain details from outside DOM elements.
Understanding composed event dispatchingPermalink to “Understanding composed event dispatching”
By default, an event dispatched inside a shadow root will not be visible outside that shadow root. To make an event pass through shadow DOM boundaries, you must set the
composed property to
true. It's common to pair
bubbles so that all nodes in the DOM tree can see the event:
If an event is
composed and does
bubble, it can be received by all ancestors of the element that dispatches the event—including ancestors in outer shadow roots. If an event is
composed but does not
bubble, it can only be received on the element that dispatches the event and on the host element containing the shadow root.
Note that most standard user interface events, including all mouse, touch, and keyboard events, are both bubbling and composed. See the MDN documentation on composed events for more information.
Understanding event retargetingPermalink to “Understanding event retargeting”
Composed events dispatched from within a shadow root are retargeted, meaning that to any listener on an element hosting a shadow root or any of its ancestors, they appear to come from the hosting element. Since Lit components render into shadow roots, all composed events dispatched from inside a Lit component appear to be dispatched by the Lit component itself. The event's
target property is the Lit component.
In advanced cases where it is required to determine the origin of an event, use the
event.composedPath() API. This method returns an array of all the nodes traversed by the event dispatch, including those within shadow roots. Because this breaks encapsulation, care should be taken to avoid relying on implementation details that may be exposed. Common use cases include determining if the element clicked was an anchor tag, for purposes of client-side routing.
See the MDN documentation on composedPath for more information.
Communicating between the event dispatcher and listenerPermalink to “Communicating between the event dispatcher and listener”
Events exist primarily to communicate changes from the event dispatcher to the event listener, but events can also be used to communicate information from the listener back to the dispatcher.
One way you can do this is to expose API on events which listeners can use to customize component behavior. For example, a listener can set a property on a custom event's detail property which the dispatching component then uses to customize behavior.
Another way to communicate between the dispatcher and listener is via the
preventDefault() method. It can be called to indicate the event's standard action should not occur. When the listener calls
preventDefault(), the event's
defaultPrevented property becomes true. This flag can then be used by the listener to customize behavior.
Both of these techniques are used in the following example: