Event: composed property

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since January 2020.

Note: This feature is available in Web Workers.

The read-only composed property of the Event interface returns a boolean value which indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM.

All UA-dispatched UI events are composed (click/touch/mouseover/copy/paste, etc.). Most other types of events are not composed, and so will return false. For example, this includes synthetic events that are created without their composed option set to true.

Propagation only occurs if the bubbles property is also true. However, capturing only composed events are also handled at host as if they were in AT_TARGET phase. You can determine the path the event will follow through the shadow root to the DOM root by calling composedPath().

Value

A boolean value which is true if the event will cross from the shadow DOM into the standard DOM after reaching the shadow root. (That is, the first node in the shadow DOM in which the event began to propagate.)

If this value is false, the shadow root will be the last node to be offered the event.

Examples

In this example, we define two trivial custom elements, <open-shadow> and <closed-shadow>, both of which take the contents of their text attribute and insert them into the element's shadow DOM as the text content of a <p> element. The only difference between the two is that their shadow roots are attached with their modes set to open and closed respectively.

The two definitions look like this:

js
customElements.define(
  "open-shadow",
  class extends HTMLElement {
    constructor() {
      super();

      const pElem = document.createElement("p");
      pElem.textContent = this.getAttribute("text");

      const shadowRoot = this.attachShadow({
        mode: "open",
      });

      shadowRoot.appendChild(pElem);
    }
  },
);

customElements.define(
  "closed-shadow",
  class extends HTMLElement {
    constructor() {
      super();

      const pElem = document.createElement("p");
      pElem.textContent = this.getAttribute("text");

      const shadowRoot = this.attachShadow({
        mode: "closed",
      });

      shadowRoot.appendChild(pElem);
    }
  },
);

We then insert one of each element into our page:

html
<open-shadow text="I have an open shadow root"></open-shadow>
<closed-shadow text="I have a closed shadow root"></closed-shadow>

Then include a click event listener on the <html> element:

js
document.querySelector("html").addEventListener("click", (e) => {
  console.log(e.composed);
  console.log(e.composedPath());
});

When you click on the <open-shadow> element and then the <closed-shadow> element, you'll notice two things.

  1. The composed property returns true because the click event is always able to propagate across shadow boundaries.
  2. A difference in the value of composedPath for the two elements.

The <open-shadow> element's composed path is this:

Array [ p, ShadowRoot, open-shadow, body, html, HTMLDocument https://mdn.github.io/web-components-examples/composed-composed-path/, Window ]

Whereas the <closed-shadow> element's composed path is a follows:

Array [ closed-shadow, body, html, HTMLDocument https://mdn.github.io/web-components-examples/composed-composed-path/, Window ]

In the second case, the event listeners only propagate as far as the <closed-shadow> element itself, but not to the nodes inside the shadow boundary.

Specifications

Specification
DOM
# ref-for-dom-event-composed①

Browser compatibility

desktop mobile server
Chrome
Edge
Firefox
Opera
Safari
Chrome Android
Firefox for Android
Opera Android
Safari on iOS
Samsung Internet
WebView Android
WebView on iOS
Bun
Deno
Node.js
composed
Chrome – Full support
Chrome 53 (Release date: 2016-08-31)
footnote Full support
Edge – Full support
Edge 79 (Release date: 2020-01-15)
footnote Full support
Firefox – Full support
Firefox 52 (Release date: 2017-03-07)
footnote
footnote Before Firefox 95, this property was incorrectly set to false on <select> and <input type='checkbox'> elements.
Opera – Full support
Opera 40 (Release date: 2016-09-20)
footnote Full support
Safari – Full support
Safari 10 (Release date: 2016-09-20)
footnote Full support
Chrome Android – Full support
Chrome Android 53 (Release date: 2016-09-07)
footnote Full support
Firefox for Android – Full support
Firefox for Android 52 (Release date: 2017-03-07)
footnote
footnote Before Firefox for Android 95, this property was incorrectly set to false on <select> and <input type='checkbox'> elements.
Opera Android – Full support
Opera Android 41 (Release date: 2016-10-25)
footnote Full support
Safari on iOS – Full support
Safari on iOS 10 (Release date: 2016-09-13)
footnote Full support
Samsung Internet – Full support
Samsung Internet 6 (Release date: 2017-08-23)
footnote Full support
WebView Android – Full support
WebView Android 53 (Release date: 2016-09-07)
footnote Full support
WebView on iOS – Full support
WebView on iOS 10 (Release date: 2016-09-13)
footnote Full support
Bun – Full support
Bun 1 (Release date: 2023-09-08)
footnote Full support
Deno – Full support
Deno 1 (Release date: 2020-05-13)
footnote Full support
Node.js – Full support
Node.js 14.5 (Release date: 2020-06-30)
footnote
footnote This is not used in Node.js and is provided purely for completeness.

Legend

Tip: you can click/tap on a cell for more information.

Full support
Full support
See implementation notes.