Where to add your event listeners

You need to add event listeners in a method that is guaranteed to fire before the event occurs. However, for optimal loading performance, you should add your event listener as late as possible.

You can add event listeners:

Using this in event handlers

The default JavaScript context object (this) inside an event handler belonging to a LitElement-based component is the component itself.

Therefore, you can use this to refer to your element instance inside any event handler:

class MyElement extends LitElement {
  render() {
    return html`<button @click="${this.handleClick}">click</button>`;
  handleClick(e) {

See the documentation for this on MDN for more information.

Use cases

Fire an event from a LitElement-based component

Fire a custom event:

class MyElement extends LitElement {
  render() {
    return html`<div>Hello World</div>`;
  firstUpdated(changedProperties) {
    let event = new CustomEvent('my-event', {
      detail: {
        message: 'Something important happened'

Fire a standard event:

class MyElement extends LitElement {
  render() {
    return html`<div>Hello World</div>`;
  updated(changedProperties) {
    let click = new Event('click');

Handle an event fired by a LitElement-based component

If you want to listen to an event fired from a LitElement-based component from within another LitElement or from a lit-html template, you can use the lit-html declarative event syntax:

<my-element @my-event="${(e) => { console.log(e.detail.message) }}"></my-element>

To listen to events fired from a LitElement-based component in other contexts, like HTML or another framework, use the standard mechanism for listening to DOM events.

In plain HTML and JavaScript, this would be the addEventListener API:

const myElement = document.querySelector('my-element');
myElement.addEventListener('my-event', (e) => {console.log(e)});

Working with events and shadow DOM

When working with events and shadow DOM, there are a few things you need to know about.

Event bubbling

Some events bubble up through the DOM tree, so that they are detectable by any element on the page.

Whether or not an event bubbles depends on the value of its bubbles property. To check if a particular event bubbles:


See the MDN documentation on the Event interface for more information.

Event retargeting

Bubbling events fired from within shadow DOM are retargeted so that, to any listener external to your component, they appear to come from your component itself.


<my-element onClick="(e) => console.log("></my-element>
render() {
  return html`
    <button id="mybutton" @click="${(e) => console.log(}">
      click me

When handling such an event, you can find where it originated from with composedPath:

handleMyEvent(event) {
  console.log('Origin: ', event.composedPath()[0]);

Custom events

By default, a bubbling custom event fired inside shadow DOM will stop bubbling when it reaches the shadow root.

To make a custom event pass through shadow DOM boundaries, you must set both the composed and bubbles flags to true:

firstUpdated(changedProperties) {
  let myEvent = new CustomEvent('my-event', { 
    detail: { message: 'my-event happened.' },
    bubbles: true, 
    composed: true });

See the MDN documentation on custom events for more information.