Shadow DOM styling from the outside

I am searching a way to styling shadow DOM from the outside. For example, I would like to set the color of all text in all ‘span.special’ elements as RED. Including ‘span.special’ elements from shadow DOM. How I can do this?

Previously there were ::shadow pseudo-element and /deep/ combinator aka >>> for this purpose. So I could write something like

span.special, *::shadow span.special {
    color: red
}

But now ::shadow, /deep/ and >>> are deprecated. So, what do we have as a replacement of them?

37 thoughts on “Shadow DOM styling from the outside”

  1. You could use @import css as explained in this answer to another question on SO.

    Include the rule inside the style element in the shadow tree.

     <style>
       @import url( '/css/external-styles.css' )
     </style>
    

    Note that the >>> combinator is still part of the CSS Scoping Module Draft.

    Reply
  2. There is still no easy way to pierce through the shadow root, but here are 3 ways you can go about it. Just keep in mind that you will need to make changes inside the web component.

    1. Using variables v1 – You will need to pass the property and consume the variable inside the web component.

    2. Using variables v2 – You will need to consume the variable inside the web component.

    3. Using ::part() – You will need to add a part attribute to the element you want to style in the web component. (Note: this pseudo element is well supported but is still in experimental mode, so make sure you’re aware of that before using it in production).

    See code below for details.

    const elA = document.querySelector('custom-container-a');
    const shadowRootA = elA.attachShadow({mode:'open'});
    shadowRootA.innerHTML = '<style>:host([border]) {display:block;border: var(--custom-border);}</style>'+
        '<p>Shadow content A</p>'
      
      
    const elB = document.querySelector('custom-container-b');
    const shadowRootB = elB.attachShadow({mode:'open'});
    shadowRootB.innerHTML = '<style>p {display:block;color: var(--custom-color, blue);}</style>'+
        '<p>Shadow content B</p>'
    
    
    const elC = document.querySelector('custom-container-c');
    const shadowRootC = elC.attachShadow({mode:'open'});
    shadowRootC.innerHTML = '<p part="paragraph">Shadow content C</p>'
    /* Normal way of styling */
    p {
      color: orange;
    }
    
    /* Using variables version 1 */
    custom-container-a {
      --custom-border: 3px solid gold;
    }
    
    /* Using variables version 2 */
    custom-container-b {
      --custom-color: green;
    }
    
    
    /* Using ::part() NOTE: The specs for this hasn't been finalized.
    So it might not be a good idea to use in production. */
    custom-container-c::part(paragraph) {
      color: magenta;
    }
    <p>Light content</p>
    <custom-container-a border></custom-container-a>
    <custom-container-b></custom-container-b>
    <custom-container-c></custom-container-c>
    Reply
  3. Well, @import is not a solution if you are working with library web component that you can’t change …

    Finally I found several ways to do it:

    1) Cascading. Styles of Shadow DOM’s host element affect Shadow DOM elements also. Not an option if you need to style a particular element of the Shadow DOM, not every.

    2) Custom properties https://www.polymer-project.org/1.0/docs/devguide/styling
    If an author of the web component provided such.

    3) In Polymer, the have Custom Mixins also https://www.polymer-project.org/1.0/docs/devguide/styling

    4) @import, but only for not-library components

    So, there are several possibilities, but all of them are limited. No powerful enough way to outside styling as ::shadow were.

    Reply
  4. I did try many methods, including those described here. Since I’m using an external Web Component lib, I don’t have access to modify these components. So, the only solution that worked for me was using JS querySelector, like this:

    document.querySelector("the-element.with-shadow-dom")
      .shadowRoot.querySelector(".some-selector").setAttribute("style", "color: black");
    

    Not the best solution, not suitable for large stylings, but does work for little enchancements.

    @John this was tested with Chrome 83.0.4103.116 (still going to test in Safari) and I did for Ionic (v5) ion-toast component. Here is the (almost) real code I used:

      import { toastController } from '@ionic/core';
    
      let toastOpts = {
        message: "Some message goes here.",
        cssClass: "toast-with-vertical-buttons",
        buttons: [
          {
            text: "Button 1",
            side: 'end'
          },
          {  
            text: "Button2",
            side: 'end'
          },
          {
            icon: "close",
            side: "start"
          }
        ]
      }
      toastController.create(toastOpts).then(async p => {
        let toast = await p.present(); // this renders ion-toast component and returns HTMLIonToastElement
        toast.shadowRoot.querySelector('div.toast-button-group-end').setAttribute("style", "flex-direction: column");
      });
    
    Reply
  5. Heya! I understand this is kind of off-topic but I
    had to ask. Does managing a well-established website like yours require a
    lot of work? I’m brand new to running a blog but I do write in my diary daily.
    I’d like to start a blog so I will be able to share
    my personal experience and views online. Please let me
    know if you have any recommendations or tips for brand new aspiring blog owners.
    Appreciate it! https://tadalafil.cleckleyfloors.com/

    Reply

Leave a Comment