Skip to content

Allow MutationObserver to observe across <slot>s #1415

Open

Description

@jpzwarte

What problem are you trying to solve?

As a custom element author, you have a couple of mechanism available to you to detect changes in slotted elements:

  1. The slotchange event
  2. MutationObserver

For most cases slotchange is enough, but if you want to detect changes to attributes for example, you need MutationObserver.

Now as long as you have no nested slots, then everything is fine. However, as soon as you have a nested slot, MutationObserver also no longer works. For example:

<x-foo>
  <template shadowrootmode="open">
    <x-bar>
      <template shadowrootmode="open">
        <slot></slot>
      </template>      
      <slot></slot>
    </x-bar>
  </template>
  <button>Button</button>
</x-foo>

In the above scenario <x-bar> would have a MutationObserver that monitors it's own host element. It will not fire when the button changes or a second button is added.

I propose a new option to the MutationObserver.observe() method called flatten or composed (name TBD) that would instruct the observer to also observe any slotted children of any slots it is observing.

What solutions exist today?

None

How would you solve it?

New flatten or composed option to MutationObserver.observe()

Anything else?

No response

Activity

  1. sorvell commented on Oct 24, 2025

    @sorvell

    This would be a useful addition.

    Right now, although it's a little clunky, I think you can leverage slotchange to do most of this:

    const myObserver = new MutationObserver(callback);
    
    let previousAssigned = new Set();
    
    slot.addEventListener('slotchange', (e) => {
      myObserver.disconnect();
      const assigned = new Set(this.observeSlot.assignedElements({flatten: true}));
      assigned.forEach(el => {
        myObserver.observe(el, {attributes: true});
      });
    
      // if needed, an process slot changes similar to other mutation observer childList mutations...
      const addedNodes = Array.from(assigned.difference(previousAssigned));
      const removedNodes = Array.from(previousAssigned.difference(assigned));
      callback([{target: slot, addedNodes, removedNodes}])
    
      previousAssigned = assigned;
    })
  2. LeaVerou commented on Dec 13, 2025

    @LeaVerou
    Member

    @sorvell slotchange doesn't fire for manual assignment, so that's another use case for this.

  3. sorvell commented on Dec 13, 2025

    @sorvell

    @sorvell slotchange doesn't fire for manual assignment, so that's another use case for this.

    I'd love to see this added to MutationObserver. This is just a point of clarification: seems like it does fire?

  4. LeaVerou commented on Dec 15, 2025

    @LeaVerou
    Member

    @sorvell slotchange doesn't fire for manual assignment, so that's another use case for this.

    I'd love to see this added to MutationObserver. This is just a point of clarification: seems like it does fire?

    It appears you're right! No idea why I had that impression, but I’m very glad I was wrong 😅 Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions