javascripthtmlcsscustom-elementshadow-root

Why do display:flex do not respect shadow root border?


If you toggle display:flex on a shadow root child it also affects the element outside. (All big browsers behave like this.) Why?

There is a web component with a shadow root:

<web-comp style="display: inline-block;"></web-comp>

Inside the shadow root there is a div with display:flex:

div.style="display:flex; align-items:center; height:50px;"

The complete example:

class demo extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({mode: 'open'});
    
    const div = document.createElement('div');
    div.innerHTML= "I am in a shadow root!"
    div.style="display:flex;align-items:center;height:50px;background:lightblue"
    shadow.appendChild(div);
  }
}
customElements.define('web-comp', demo);
  <h3>flexbox styles do not respect shadow root border</h3>

   <web-comp style="display: inline-block;"></web-comp>
And I am not.

   <button onclick="document.querySelector('web-comp').shadowRoot.querySelector('div').style.alignItems='baseline'">
   Click to change 'align-items' of div in shadow root.
   </button>


Solution

  • I understand your confusion as this actually does look like styles from within the shadow root “bleeding out” their shadow boundaries.

    However, all the spec guarantees is that rules declared inside the shadow root don’t apply to elements outside. It does not prevent second-order layout effects to affect the rendering of outside elements.

    One trivial example where this happens is when an element inside shadow root changes size:

    const hasShadow = document.querySelector('.has-shadow');
    const shadow = hasShadow.attachShadow({mode: 'closed'});
    const div = document.createElement('div');
    while(hasShadow.firstChild) {
    div.append(hasShadow.firstChild);
    }
    shadow.append(div);
    document.querySelector('button').addEventListener('click', () => {
    div.style.height = `${Math.random()*100+25}px`;
    });
    .wrapper {
      display: flex;
    }
    .has-shadow {
    background: rebeccapurple;
    }
    .no-shadow {
    background: red;
    }
    <button>Change shadow size</button>
    <div class="wrapper">
      <div class="has-shadow">I can haz shadow</div>
      <div class="no-shadow">I can haz light</div>
    </div>

    Here, when the element in shadow root changes its height, the element outside also has to change height. This may be obvious because we encounter it all the time that we don’t give it much thought.

    What’s happening in your case is very similar, just not with height but with baseline.

    Because your shadow-root-containing element is set to inline block, it takes part in a baseline-sharing group, the same one that also contains the text “And I am not.”

    Now, why, you may ask, does the baseline-sharing group extend into the inner <div>. Doesn’t that generate a block, since it is set to display: flex, not display: inline-flex? Well, this is true, but, inside an inline-block element, this does not break out of the baseline-sharing group. There is nothing special about the shadow root in this, non-shadow elements behave the same way:

    <div style="display: inline-block;">
      <div style="display:flex;align-items:center;height:50px;background:lightblue">
        I am in an inline-block!
      </div>
    </div>
    And I am not.