添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams
Closed 9 months ago .

The community reviewed whether to reopen this question 9 months ago and left it closed:

Original close reason(s) were not resolved

The CSS ::slotted selector selects children of the <slot> element.

However, when trying to select grandchildren like with ::slotted(*) , ::slotted(*) * , or ::slotted(* *) , the selector doesn't seem to take effect.

class MyElement extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'open'})
    shadowRoot.innerHTML = `
      <style>
        ::slotted(*) {
          display: block;
          border: solid blue 1px;
          padding: 3px;
        ::slotted(*) span {
          display: block;
          border: solid red 1px;
          padding: 3px;
        ::slotted(* span) {
          display: block;
          border: solid green 1px;
          padding: 3px;
      </style>
      <slot></slot>
customElements.define('my-element', MyElement);
<my-element>
    <span>Test</span>
</my-element>

Note how the span doesn't get the border.

Is this expected behavior? I wasn't able to find concrete documentation for this.

If yes, is there a way to work around this?

  • ::slotted Specs: https://drafts.csswg.org/css-scoping/#slotted-pseudo

  • slotted content remains in light DOM , is reflected to a <slot> in shadow DOM

  • ::slotted(x) targets the lightDOM outer-Element (aka 'skin'), NOT the SLOT in shadowDOM

  • ::slotted(x) takes basic selectors

  • Inheritable styles trickle into shadowDOM
    https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/

  • For the latest WHATWG discussion on SLOT and related topics, see

  • https://github.com/whatwg/html/issues/6051#issuecomment-816971072
    Participants: rniwa (Apple) , annvk (Mozilla), dominic (Google)
  • https://github.com/WICG/webcomponents/issues/934#issuecomment-906063140
  • Interesting reads:

  • A history of the HTML <slot> element by Jan Miksovsky

  • Summary of positions on contentious bits of Shadow DOM — Web Components F2F on 2015-04-24

    background

    Yes, ::slotted() not styling nested elements is expected behavior.

    The term slotted is counterintuitive,
    it implies element lightDOM is moved to shadowDOM

    slotted lightDOM is NOT moved , it remains.. hidden.. in lightDOM
    the content (IF slotted) is reflected to a <slot></slot>

    Or from Google Developer Documentation

    𝘾𝙤𝙣𝙘𝙚𝙥𝙩𝙪𝙖𝙡𝙡𝙮, 𝙙𝙞𝙨𝙩𝙧𝙞𝙗𝙪𝙩𝙚𝙙 𝙣𝙤𝙙𝙚𝙨 𝙘𝙖𝙣 𝙨𝙚𝙚𝙢 𝙖 𝙗𝙞𝙩 𝙗𝙞𝙯𝙖𝙧𝙧𝙚.
    𝙎𝙡𝙤𝙩𝙨 𝙙𝙤𝙣'𝙩 𝙥𝙝𝙮𝙨𝙞𝙘𝙖𝙡𝙡𝙮 𝙢𝙤𝙫𝙚 𝘿𝙊𝙈; 𝙩𝙝𝙚𝙮 𝙧𝙚𝙣𝙙𝙚𝙧 𝙞𝙩 𝙖𝙩 𝙖𝙣𝙤𝙩𝙝𝙚𝙧 𝙡𝙤𝙘𝙖𝙩𝙞𝙤𝙣 𝙞𝙣𝙨𝙞𝙙𝙚 𝙩𝙝𝙚 𝙨𝙝𝙖𝙙𝙤𝙬 𝘿𝙊𝙈.

    I use the term reflected instead of render because render implies you can access it in shadowDOM.
    You can not , because slotted content isn't in shadowDOM... only reflected from lightDOM.

    Why :slotted has limited functionality

    More advanced shadowDOM styling was tried.

    WebComponents version 0 (v0) had <content> and ::content ; but it was removed from the spec:
    https://developer.mozilla.org/en-US/docs/Web/HTML/Element/content

    The main takeway from the W3C standards discussions
    (@hayatoito (Google team) here and here ) is:

    So in V1 we have :slotted : https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted

    Addition #1 : Performance if ::slotted allowed for complex selectors

    From Mozilla developer Emilio:

    source: https://github.com/w3c/webcomponents/issues/889

    The performance issue is that it increments the amount of subtrees in which every node needs to go look for rules that affect to them.

    Right now the logic goes like: if you're slotted, traverse your slots and collect rules in their shadow trees as needed. This is the code This is nice because the complexity of styling the element depends directly on the complexity of the shadow trees that you're building, and it only affects slotted nodes.

    If you want to allow combinators past slotted then every node would need to look at its ancestor and prev-sibling chain and look at which ones of them are slotted, then do that process for all their slots. Then, on top, you also need to change the general selector-matching code so that selectors that do not contain slotted selectors don't match if you're not in the right shadow tree.

    That's a cost that you pay for all elements, regardless of whether you use Shadow DOM or ::slotted, and is probably just not going to fly.

    So due to performance issues

    :slotted( S ) got limited CSS selector functionality:

  • ► it only takes simple selectors for S. --> Basically anything with a space won't work

  • ► it only targets lightDOM 'skin' . --> In other words, only the first level

    <my-element>
      <h1>Hello World</h1> 
      <p class=foo>
        <span>....</span>
      <p class=bar>
        <span>....</span>
    </my-element>
    
  • ::slotted(h1) and ::slotted(p) works

  • ::slotted(.foo) works

  • ::slotted(span) (or anything deeper) will not work (not a 'skin' element)

    Note: ::slotted([Simple Selector]) confirms to Specificity rules,
    but (being simple) does not add weight to lightDOM skin selectors, so never gets higher Specificity.
    You might need !important in some (rare) use cases.

     <style>
      ::slotted(H1) {
        color: blue !important;
     <style>
    

    Styling slotted content

    Also see: Applying more in depth selection to the :host CSS pseudo class

    #1 - style lightDOM

    The <span> is hidden in lightDOM, any changes made there will continue to reflect to its slotted representation.

    That means you can apply any styling you want with CSS in the main DOM
    (or a parent shadowDOM container if you wrapped <my-element> in one)

     <style>
      my-element span {
        .. any CSS you want
     <style>
    

    #2 - (workaround) move lightDOM to shadowDOM

    If you move lightDOM to shadowDOM with: this.shadowRoot.append(...this.childNodes)

    you can do all styling you want in a shadowDOM <style> tag.

    Note: You can not use <slot></slot> and :slotted() anymore now.
    <slot>s only works with content reflected from lightDOM.

    For an example where an element wraps itself in an extra shadowDOM layer,
    so no CSS bleeds out, and <slot>s can be used, see:

  • https://jsfiddle.net/WebComponents/5w3o2q4t/?slotmeister
  • #3 - ::part (shadow Parts)

    It is a different/powerful way of styling shadowDOM content:

    Apple finally implemented shadowParts in Safari 13.1, March 2020

  • https://meowni.ca/posts/part-theme-explainer/

  • https://css-tricks.com/styling-in-the-shadow-dom-with-css-shadow-parts/

  • https://dev.to/webpadawan/css-shadow-parts-are-coming-mi5

  • https://caniuse.com/mdn-html_global_attributes_exportparts

    Note! ::part styles shadowDOM,
    <slot></slot> content remains in lightDOM!

    references

    be aware: might contain v0 documentation!

  • https://css-tricks.com/encapsulating-style-and-structure-with-shadow-dom/

  • https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=en#composition_slot

  • https://polymer-library.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-your-elements

  • https://github.com/w3c/webcomponents/issues/331

  • https://github.com/w3c/webcomponents/issues/745

  • https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/slotchange_event

  • ::part() - https://developer.mozilla.org/en-US/docs/Web/CSS/::part

    Example: Using slots as a router

    Change the slot-name on buttonclick and reflect content from lightDOM:

    <template id=MY-ELEMENT>
      <style>
        ::slotted([slot="Awesome"]){
          background:lightgreen
      </style>
      <slot><!-- all unslotted content goes here --></slot>
      <slot id=answer name=unanswered></slot>
    </template>
    <style>/* style all IMGs in lightDOM */
      img { max-height: 165px;border:3px dashed green }
      img:hover{ border-color:red }
    </style>
    <my-element><!-- content below is: lightDOM! -->
      SLOTs are: <button>Cool</button> <button>Awesome</button> <button>Great</button>
      <span slot=unanswered>?</span>
      <div  slot=Cool>   <img src="https://i.imgur.com/VUOujQT.jpg"></div>
      <span slot=Awesome><b>SUPER!</b></span>
      <div  slot=Awesome><img src="https://i.imgur.com/y95Jq5x.jpg"></div>
      <div  slot=Great>  <img src="https://i.imgur.com/gUFZNQH.jpg"></div>
    </my-element>
    <script>
      customElements.define('my-element', class extends HTMLElement {
        connectedCallback() {
          this.attachShadow({mode:'open'})
              .append(document.getElementById(this.nodeName).content.cloneNode(true));
          this.onclick = (evt) => {
               const label = evt.composedPath()[0].innerText; // Cool,Awesome,Great
               this.shadowRoot.getElementById("answer").name = label;
    </script>
    Regarding that performance comment, no evidence was provided. We need to see numbers. There are plenty of APIs that are "slow" in the web, and they aren't all bad when they are used correctly. – trusktr Mar 22, 2021 at 5:24 @trusktr Yeah… Having a bad performance is a terrible reason to remove a feature. eg: Animating width performs terribly, but it's up to the developer to do it or not. – Felds Liscia Jun 5, 2021 at 18:09 @FeldsLiscia So true. And in most cases, even on the slowest low-end phones, probably even on smart watches, such an animation would work fine. Animating one or two things is different than, say, animating 100 or 1000 things. Developers should have options, and know when to use them. Documentation is important here. – trusktr Jun 7, 2021 at 4:33 How does slotted help in case of nested web components I mean a wc nested inside another wc, but via slots – Deepak Yadav Jul 9, 2022 at 10:29
  •