0

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?

danronmoon
  • 3,814
  • 5
  • 34
  • 56
backspaces
  • 3,802
  • 6
  • 34
  • 58

1 Answers1

2

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>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • Thanks for the quick response! I'm late responding because I gotta admit I'm way noob on the shadow dom and other parts of web components like templates and slots. When I first saw your answer I said .. Whaat? But it clearly works. Can you walk through your answer, pointing out what/why of the various parts? For example: the innerHTML with innerHTML = ` etc? What does do? Etc! I'm basically looking for how to style children and I'll tease out an answer from the code but some commentary would help. Thanks!! – backspaces Jul 03 '23 at 21:03
  • ```` will _**reflect**_ the **lightDOM** content (the HTML inside ````. There are a lot of concepts to learn. https://javascript.info/web-components is a good starting point. – Danny '365CSI' Engelman Jul 04 '23 at 07:24