Skip to content

Custom Events

This lesson explains Custom Events with clear examples, use cases, and best practices so you can build reusable Web Components confidently.

What Are Custom Events?

Custom Events allow Web Components to communicate with the outside page, parent components, or application JavaScript by dispatching their own events.

Instead of exposing internal methods or DOM structure, a Web Component can send meaningful events like user-selected, cart-updated, or modal-closed.

Concept Description
CustomEvent JavaScript API used to create custom browser events
dispatchEvent() Sends the event from the component
detail Object used to pass custom data with the event
bubbles Allows the event to bubble up through the DOM
composed Allows the event to cross the Shadow DOM boundary
Main Benefit Clean communication from Web Components to application code

Basic Custom Event Example

const event = new CustomEvent("user-selected", {
  detail: {
    id: 101,
    name: "John"
  }
});

element.dispatchEvent(event);

The event name is user-selected, and custom data is passed through the detail object.

Custom Events in Web Components

class AppButton extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" });

    this.shadowRoot.innerHTML = `
      <button type="button">
        <slot>Click Me</slot>
      </button>
    `;
  }

  connectedCallback() {
    const button = this.shadowRoot.querySelector("button");

    button.addEventListener("click", () => {
      this.dispatchEvent(new CustomEvent("app-click", {
        detail: {
          message: "Button clicked"
        },
        bubbles: true,
        composed: true
      }));
    });
  }
}

customElements.define("app-button", AppButton);
<app-button>Save</app-button>

<script>
  const button = document.querySelector("app-button");

  button.addEventListener("app-click", event => {
    console.log(event.detail.message);
  });
</script>

The component dispatches an app-click event when the internal button is clicked. The outside page listens to the event without accessing the internal Shadow DOM.

How Custom Events Work

Step What Happens
1. Create event Use new CustomEvent("event-name")
2. Add data Pass custom data using detail
3. Dispatch event Send it with dispatchEvent()
4. Enable bubbling Use bubbles: true when parent elements should hear it
5. Cross Shadow DOM Use composed: true when the event must leave Shadow DOM
6. Listen outside Use addEventListener() on the component or parent

Passing Data With detail

this.dispatchEvent(new CustomEvent("product-added", {
  detail: {
    id: "p101",
    name: "Wireless Mouse",
    price: 29.99,
    quantity: 1
  },
  bubbles: true,
  composed: true
}));
document.addEventListener("product-added", event => {
  console.log(event.detail.name);
  console.log(event.detail.quantity);
});

Use detail to pass useful information from the component to the outside application.

bubbles and composed

Option Meaning Common Value
bubbles Allows the event to move up through parent elements true
composed Allows the event to cross Shadow DOM boundary true
cancelable Allows listeners to call preventDefault() true when action can be canceled
this.dispatchEvent(new CustomEvent("modal-close", {
  detail: {
    reason: "escape-key"
  },
  bubbles: true,
  composed: true,
  cancelable: true
}));

For most Web Component communication, use bubbles: true and composed: true so parent components can listen to the event.

Cancelable Custom Events

const event = new CustomEvent("before-delete", {
  detail: {
    id: 10
  },
  bubbles: true,
  composed: true,
  cancelable: true
});

const allowed = this.dispatchEvent(event);

if (!allowed) {
  console.log("Delete action was canceled");
  return;
}

this.deleteItem();
document.addEventListener("before-delete", event => {
  const confirmDelete = window.confirm("Delete this item?");

  if (!confirmDelete) {
    event.preventDefault();
  }
});

Cancelable events allow outside code to stop an action before the component continues.

Custom Event Naming Patterns

Pattern Example Use
Past action item-selected After something already happened
Before action before-delete Before an action that can be canceled
State change value-changed When component state changes
Component prefix app-modal-close Avoid naming conflicts in large apps
Kebab case cart-item-added Recommended naming style for custom events

Listening From a Parent Element

<section id="cart">
  <product-card product-id="101"></product-card>
</section>

<script>
  const cart = document.querySelector("#cart");

  cart.addEventListener("product-added", event => {
    console.log("Product added:", event.detail);
  });
</script>

Because the event bubbles, the parent element can listen for events from child Web Components.

Custom Events and Shadow DOM

this.shadowRoot.querySelector("button").addEventListener("click", () => {
  this.dispatchEvent(new CustomEvent("user-action", {
    detail: {
      action: "save"
    },
    bubbles: true,
    composed: true
  }));
});

When using Shadow DOM, composed: true is important because it allows the event to leave the component and reach external listeners.

Custom Events vs Callback Properties

Feature Custom Events Callback Properties
HTML Friendly Yes, works naturally with DOM events No, requires JavaScript property assignment
Multiple Listeners Supports many listeners Usually one callback
Parent Communication Good for parent and app-level communication Good for direct JavaScript control
Shadow DOM Works with composed: true Does not bubble through DOM
Recommended For Reusable Web Component APIs Special advanced use cases

When to Use Custom Events

  • You want a Web Component to notify the outside page about actions.
  • You need parent components to listen for child component changes.
  • You want to avoid exposing internal Shadow DOM structure.
  • You are building reusable design system components.
  • You need multiple listeners for the same component event.
  • You want framework-independent communication.

Common Custom Event Use Cases

  • Button clicked: app-click
  • Modal closed: modal-closed
  • Tab selected: tab-selected
  • Form submitted: form-submitted
  • Input value changed: value-changed
  • Product added to cart: product-added

Custom Events Best Practices

  • Use clear event names like item-selected or value-changed.
  • Use kebab-case for custom event names.
  • Pass useful data through detail.
  • Use bubbles: true for parent-level communication.
  • Use composed: true when events must cross Shadow DOM.
  • Use cancelable: true only when outside code can stop the action.
  • Document event names and detail payload structure.

Common Custom Event Mistakes

  • Forgetting to use composed: true inside Shadow DOM.
  • Forgetting bubbles: true when parent listeners are needed.
  • Using unclear event names like change or click2.
  • Putting too much unrelated data inside detail.
  • Dispatching events before the component is ready.
  • Using callbacks when native DOM events would be cleaner.
  • Not documenting custom events for component consumers.

Key Takeaways

  • Custom Events help Web Components communicate with the outside page.
  • Use new CustomEvent() to create a custom event.
  • Use detail to pass custom data.
  • Use bubbles: true for parent listeners.
  • Use composed: true when working with Shadow DOM.
  • Custom Events are a clean public API for reusable components.

Pro Tip

For Web Components with Shadow DOM, most public events should use bubbles: true and composed: true so application code can listen from parent elements without touching internal markup.