How to select an element in a shadow root?

A JavaScript popup, represented in a shadow root or alternatively in a shadow DOM, contains an element which I want to remove, so to remove it, I have tried the folliwing commands in my web browser’s console.

document.querySelector('.Button__button--11-4-12').remove();

Uncaught TypeError: Cannot read properties of null (reading ‘remove’)
at :1:51

document.shadowRoot.querySelector('.Button__button--11-4-12').remove();

Uncaught TypeError: Cannot read properties of undefined (reading ‘querySelector’)
at :1:21

document.querySelectorAll('*').forEach((element) => {
    if (element.classList.contains('Button__button--11-4-12')) {
        element.remove();
    }
});

undefined

What have I done wrong?

Hi @bendqh1, you can’t query for elements under a shadow root from “outside”, you need to know the host element in advance; then you can query that element’s shadow DOM like e.g.:

const popupElement = document.getElementById('my-popup')
const button = popupElement.shadowRoot.querySelector('.Button__button--11-4-12')
// ...

Note that the shadow root might also have been attached in closed mode though, in which case you won’t be able to access its contents at all.

PS: As for your last snippet, I suppose you might iterate over all elements in the document like this and check if they have a shadow root attached; and if so, query that shadow root with your selector.

2 Likes

It apears to me that in this particular case, the shadowRoot is open:

classes and shadowRoot


This code didn’t work:

const popupElement = document.querySelector('.spotimWrapper') // Select Wrapper
const myButton = popupElement.shadowRoot.querySelector('.Button__button--11-4-12'); // Select element in shadowRoot inside the wrapper
myButton.remove();

PS: As for your last snippet, I suppose you might iterate over all elements in the document like this and check if they have a shadow root attached; and if so, query that shadow root with your selector.

Based on my interpretation of that, I have checked the following code that didn’t work.

document.querySelectorAll('shadowRoot').forEach((element) => {
    if (element.classList.contains('Button__button--11-4-12')) {
        element.remove();
    }
});

Okay no problem here then. :-)

Well that’s not the host of the shadow root; the host element would be the one with the cryptic tag name ow-j3eolsu49kg beneath. Since this looks very much generated and likely to change in the future, you might use a more robust selector though… e.g. from the parent element:

const host = document.querySelector('[data-spotim-module] > *')

Ah no that won’t work. I meant something like this:

document.querySelectorAll('*').forEach(element => {
  // No shadow root? Continue.
  if (!element.shadowRoot) {
    return
  }

  // Now query the *shadow root* for your button element...
  const button = element.shadowRoot.querySelector('.Button__button--11-4-12')

  // ... and remove it if it's there.
  if (button) {
    button.remove()
  }
})

Or concise with optional chaining:

document.querySelectorAll('*').forEach(element => {
  element.shadowRoot
    ?.querySelector('.Button__button--11-4-12')
    ?.remove()
})
2 Likes

Sadly, both last code examples didn’t work — the button wasn’t removed.
The console output for both was:

undefined

document.querySelectorAll('*').forEach(element => {
    element.shadowRoot.querySelector('.Button__button--11-4-12').remove();
})

Uncaught TypeError: Cannot read properties of null (reading ‘querySelector’)
at :2:39
at NodeList.forEach ()
at :1:32

You missed out the check if the element actually has a shadow root attached (or the optional chaining operator, if you prefer this approach).

Yes, I did that on purpose becaue I assumed there is indeed a shadow root attached.
Anyway, why then will the other two codes (that do have this shadow root test) won’t work?

I just want to select all button elements in all (opened) shadow root elements, anywhere, in this case.

Not every element on the page necessarily has a shadow root attached. This however…

document.querySelectorAll('*')

… will give you all elements on the page.

If you’re trying to query the shadow root of an element, and the element just doesn’t have shadow root attached, you’ll get an error. Hence the need for a prior check.

Try this:

let shadowRoot = document.querySelector('#hostElementId').shadowRoot;
let buttonElement = shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
    buttonElement.remove();
}

Replace the host element’s selector.

Sadly it didn’t work - the button isn’t removed.

let shadowRoot = document.querySelector('.spotimWrapper').shadowRoot;
let buttonElement = shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
    buttonElement.remove();
}

Uncaught TypeError: Cannot read properties of null (reading ‘querySelector’)
at :2:32

let shadowRoot = document.querySelector('ow-c73itcdk6u').shadowRoot;
let buttonElement = shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
    buttonElement.remove();
}

undefied

:expressionless:

The error “Cannot read properties of null (reading ‘querySelector’)” means that the document.querySelector('.spotimWrapper') and document.querySelector('ow-c73itcdk6u') didn’t return any element. This could be because the selectors are not correct or the elements might not have been rendered yet when the code ran.

Ensure that the element hosting the shadow root (shadow host) is correctly selected. Then access its shadow root.
Using the browser’s developer tools, Inspect the shadow host (the element that contains the shadow root) and note down its unique attributes or class names. Once you’ve identified the shadow host, use its attributes or class names in the querySelector to get a reference to it. Use the .shadowRoot property on the shadow host to access its shadow root. then use the shadow root reference to query for the button and remove it.

let shadowHost = document.querySelector('shadowHostSelector');
if (shadowHost && shadowHost.shadowRoot) {
    let buttonElement = shadowHost.shadowRoot.querySelector('.Button__button--11-4-12');
    if (buttonElement) {
        buttonElement.remove();
    }
}

Also check if the shadow root is added to the DOM dynamically, if yes, you need to modify the entire process.

2 Likes

Hello !

I have double checked the selector names and they were correct.

The element was already loaded, and I have actually done all code test from browser console (i.e not from a user script manager).

Sadly the above code didn’t work as well.

Also check if the shadow root is added to the DOM dynamically, if yes, you need to modify the entire process.

I think that’s spot on !

Indeed, the entire wrapper element and it’s shadow root that “both” contain the button are dynamically added to the DOM (i.e they do not appear after I refresh the page).

So, it seems that I do indeed need to modify the enitre process :slight_smile: but how should I start go about doing that?

Actually my main purpose is not to remove the button but to click on it (which doesn’t work either because it’s element can’t be selected).

I might as well just use a macro action to achieve that:

The dynamically loaded elements within a shadow DOM are not easily accessible through conventional DOM querying methods. I will give you two logical solutions that can be broken down into potential pathways:

1. Macro Recorder Solution:

Given the challenges in programmatically accessing elements within a shadow DOM, especially when elements are dynamically loaded, a Macro Recorder such as Jitbit may provide a more straightforward solution. Here’s how you could proceed:

  • Install a Macro Recorder tool.
  • Manually perform the actions of navigating to the button and clicking it while the Macro Recorder is running to capture these actions.
  • Save this recorded macro, and run it whenever you need to automate the clicking of the button.

This approach essentially bypasses the need to interact with the DOM programmatically, and instead, relies on replicating user interactions to achieve the desired outcome.

2. Programmatic Solution:

A more sophisticated approach would be needed to handle the dynamically loaded shadow DOM elements if a programmatic solution is preferred. Here’s a step-by-step approach:

  • Step 1: Identify when the shadow DOM (along with the button) is loaded into the DOM. This could be done by monitoring mutations to the DOM using a MutationObserver.
  • Step 2: Once the shadow DOM is loaded, access the shadow root of the host element that encapsulates the shadow DOM.
  • Step 3: Utilize the shadowRoot.querySelector() method to target and interact with the button within the shadow DOM.
//Let's say shadowHostSelector is the selector for the host element of the shadow DOM
let shadowHost = document.querySelector('shadowHostSelector');

// Create an instance of MutationObserver
let observer = new MutationObserver((mutationsList, observer) => {
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList' && shadowHost.shadowRoot) {
            let buttonElement = shadowHost.shadowRoot.querySelector('.Button__button--11-4-12');
            if(buttonElement) {
                buttonElement.click();  // Or buttonElement.remove(); to remove the button
                observer.disconnect();  // Stop observing once the button is found and clicked
            }
        }
    }
});

// Start observing the document with configured parameters
observer.observe(document, { childList: true, subtree: true });

This programmatic approach uses a MutationObserver to wait for the shadow DOM to be loaded, then accesses and interacts with the button within the shadow DOM.

Either of these pathways could provide a viable solution to your problem.

Good Luck

1 Like

Hi !

I want to do it directly without observing a DOM mutation.

user:kicken was kind to share with us a working solution here:

I admit I don’t yet fully understand the code he used, but it was the only one that worked for me.

An HTML example is also shared there on the linked page, you are welcome to check it !

Thanks a lot !

Access the Shadow Host Element : First, you need to access the shadow host element. This is the element to which the shadow root is attached. You can do this using standard DOM methods like querySelector or by selecting the element by its ID, class, or any other selector.

const shadowHost = document.querySelector('#your-shadow-host-element');

Access the Shadow Root : Next, you’ll access the shadow root using the shadowRoot property.

const shadowRoot = shadowHost.shadowRoot;

Select Elements within the Shadow Root : Now that you have the shadow root, you can use the querySelector or other DOM methods to select elements within it, just like you would with the main DOM. The only difference is that you’re selecting elements within the shadow DOM.

const elementInShadowRoot = shadowRoot.querySelector('#element-inside-shadow-root');

JavaScript:
// Access the shadow host element
const shadowHost = document.querySelector(‘#myShadowHost’);

// Access the shadow root
const shadowRoot = shadowHost.shadowRoot;

// Select an element within the shadow root
const buttonInShadow = shadowRoot.querySelector(‘#buttonInShadow’);

1 Like

No meaning to be annoying but without an HTML example such as the one I gave in the above linked thread I didn’t succeed to undersand your comment. You may want to expand based on the HTML there.

you can use the composed option with the getRootNode() method to access the shadow DOM

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.