Search code examples
cssparent-childweb-componentshadow-domcustom-element

How do I style custom element children


I am building a flex custom element which needs to style its children as well as itself. It is a "stack" element that sets its children to have a uniform vertical spacing. (This comes from reading every layout)

The html looks like:

<test-stack>
    <h3>Hello World</h3>
    <p>I'm a paragraph</p>
    <div>And I'm a div with text</div>
</test-stack>

The simplified custom element is:

class TestStack extends HTMLElement {
    constructor() {
        super()
        this.attachShadow({ mode: 'open' })
    }
    render() {
        this.shadowRoot.innerHTML = `
            <style>
                :host {
                    display: flex;
                    flex-direction: column;
                    justify-content: flex-start;

                }
                :host > * {
                    margin-block: 0;
                }
                :host > * + * {
                    margin-block-start: 2rem;
                }
            </style>
        `
        console.log('render', this.shadowRoot.innerHTML);
    }

    connectedCallback() {
        this.render()
    }
    attributeChangedCallback() {
        this.render()
    }
}
customElements.define('test-stack', TestStack)

NOTE: The two selectors

:host > *
:host > * + *

.. obviously do not work but are examples of my trying to style all the children of a test-stack custom element.

So how does a custom element style it's children as well as itself? Flex is a good example of custom elements needing to do this. Maybe simply not possible with the shadow DOM?


Solution

  • With shadowDOM you have to slot lightDOM in a <slot>

    slotted lightDOM is styled by the container it is in! In this case the main document
    also see: ::slotted CSS selector for nested children in shadowDOM slot

    And don't forget to read about: https://developer.mozilla.org/en-US/docs/Web/CSS/::part

    If you want fancy styling inside shadowDOM, you have to add an extra <div> container element.

    Here are both methods in one Web Component:
    you will ofcourse not copy the style, but style the DIV in the shadowRoot innerHTML

    <style id="STYLE">
      test-stack {
        display: flex; flex-direction: column; justify-content: flex-start;
      }
      test-stack > * {
        background: lightgreen; margin-block: 0;
      }
      test-stack > * + * {
        background: pink; margin-block-start: .5rem;
      }
    </style>
    <test-stack id="STACK">
      <h3>Hello World</h3>
      <p>I'm a paragraph</p>
      <div>And I'm a div with text</div>
    </test-stack>
    <script>
      customElements.define('test-stack', class extends HTMLElement {
        constructor() {
          super().attachShadow({mode: 'open'})
                 .innerHTML = `<style></style>
                               slotted: <slot></slot>
                               copied: <div></div>`
        }
        connectedCallback(){
          // COPY STYLE AND HTML for demo purpose only
          let style = STYLE.innerHTML.replaceAll("test-stack","div");
          this.shadowRoot.querySelector("style").innerHTML = style;
          this.shadowRoot.querySelector("div").innerHTML = STACK.innerHTML;
        }
      })
    </script>