user4385532
user4385532

Reputation:

How can I style elements used to construct custom elements?

class MyElement extends HTMLElement {
    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'})
        shadow.appendChild(document.createTextNode('Yadda blah'))
        const span = document.createElement('span')
        span.appendChild(document.createTextNode('Can I style U?'))
        shadow.appendChild(span)
    }
}
customElements.define('my-element', MyElement)
my-element {
    border: 1px solid black;
}

span {
    font-weight: bold;
}
<my-element></my-element>

As you can see, my-element is styled, however, the span used within my-element is not. Not even saying my-element span {font-weight: bold;} in the stylesheet makes any styles effective.

Is there any way to apply styles to this span?

Upvotes: 5

Views: 480

Answers (2)

rovyko
rovyko

Reputation: 4577

You have to apply the style to the shadow DOM. A common but soon-to-be-outdated technique is to add a <style> element to the shadow DOM and set its text contents as the CSS you want to apply within the custom element.

The snippet below does just that. The style is saved to a variable, but you can place it anywhere as long you're conscious of how often its context is run. Following Intervalia's advice, it's best to apply a style to the custom element using :host.

It's also possible to add a <link> element instead, but that may cause the element to pop-in without a style (FOUC)

const myElementStyle = `
:host {
    border: 1px solid black;
}
span {
    font-weight: bold;
}
`

class MyElement extends HTMLElement {
    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'});
        const style = document.createElement('style');
        style.textContent = myElementStyle;
        shadow.appendChild(style);
        
        shadow.appendChild(document.createTextNode('Yadda blah'));
        const span = document.createElement('span');
        span.appendChild(document.createTextNode('Can I style U?'));
        shadow.appendChild(span);
    }
}
customElements.define('my-element', MyElement)
<my-element></my-element>

Another option is to use Constructable Stylesheets, which should soon be available in modern browsers. These let you create a stylesheet object that can embed styles or import styles from external resources.

I wrote up an answer here, so I'll just post a snippet that answers your question:

const customSheet = new CSSStyleSheet();
customSheet.replaceSync(`
:host {
  border: 1px solid black;
}
span {
  font-weight: bold;
}
`);

class MyElement extends HTMLElement {
  constructor() {
    super()
    const shadow = this.attachShadow({
      mode: 'open'
    })

    shadow.adoptedStyleSheets = [customSheet];

    shadow.appendChild(document.createTextNode('Yadda blah'))
    const span = document.createElement('span')
    span.appendChild(document.createTextNode('Can I style U?'))
    shadow.appendChild(span)
  }
}
customElements.define('my-element', MyElement)
<my-element></my-element>

Upvotes: 2

Hugo Silva
Hugo Silva

Reputation: 6948

That is the expected behavior of shadow dom. Since your HTML looks like <my-element></my-element>, you can target my-element from your css, but the span is not part of the actual dom, so you can't target it.

You can only apply styles to the span from within the shadow dom context.

There are better ways of defining css in this case, but just to make a point, the following apply styles to the span, but not to my-element:

class MyElement extends HTMLElement {
    constructor() {
        super()
        const shadow = this.attachShadow({mode: 'open'})
        shadow.appendChild(document.createTextNode('Yadda blah'))
        const span = document.createElement('span')
        span.appendChild(document.createTextNode('Can I style U?'))
        shadow.appendChild(span)
        const styleTag = document.createElement('style')
        styleTag.innerHTML = `
            my-element {
                border: 1px solid black;
            }
            span {
                font-weight: bold;
            }
        `
        shadow.appendChild(styleTag)
    }
}
customElements.define('my-element', MyElement)    
<my-element></my-element>

Upvotes: 1

Related Questions