slot
is good to make a reusable web component, however, it has a limit so far. What I faced is the style problem. You just can\'t define the style inside a comp
With the exception of a few inheritable rules the contents of the slot are not supposed to be directly affected by your component's shadow CSS. They are designed to allow the CSS outside of your component to be in control.
That is by design.
This is similar to the protection given to elements within the shadow DOM not being affected by external CSS.
Read the section Styling distributed nodes found here: https://developers.google.com/web/fundamentals/web-components/shadowdom#stylinglightdom
You are only allowed to change CSS rules for the top level elements inside the slot. And you are even limited to what you can do to that. All child elements are controlled by the CSS outside of the shadow DOM.
In the example below you will see that we can change the color and background color of the top level elements, or the <ul>
tags:
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
::slotted(ul)
{
color: blue;
}
::slotted(.bold) {
font-weight: bold;
background-color: #222;
color: #FFF;
}
::slotted(.italic) {
font-style: italic;
background-color: #AAA;
color: #000;
}
::slotted(*)
{
color: red;
}
</style>
<slot></slot>
`;
}
});
<my-navbar>
<ul class="bold">
<li>link1</li>
<li class="italic">link2</li>
<li>link3</li>
</ul>
<ul class="italic">
<li>link1</li>
<li class="bold">link2</li>
<li>link3</li>
</ul>
</my-navbar>
In the example above, the only reason the text is red and not blue is because ::slotted(*)
affects just the two <ul>
, has the same specificity as ::slotted(ul)
and is placed after ::slotted(ul)
. The color is inherited by the <li>
tags because that is how CSS works.
The background colors only affect the <ul>
tags based on their classes and not the <li>
tags with identical classes.
In the example below, the <li>
color and background-color are controlled by the CSS outside the shadow DOM. The external rules act as if they are more specific then the shadow DOM rules even though the shadow DOM rules included both a tag
and a class
selector (ul.bold
).
Again, this is by design.
customElements.define('my-navbar', class extends HTMLElement {
constructor () {
super();
const sr = this.attachShadow({ mode: 'open' });
sr.innerHTML = `
<style>
::slotted(ul)
{
color: blue;
}
::slotted(ul.bold) {
font-weight: bold;
background-color: #222;
color: #FFF;
}
::slotted(ul.italic) {
font-style: italic;
background-color: #AAA;
color: #000;
}
::slotted(*)
{
color: red;
}
</style>
<slot></slot>
`;
}
});
li {
color: #555;
backgroung-color: #ffffd;
}
.bold {
font-weight: bold;
background-color: #FF0;
}
.italic {
font-style: italic;
background-color: #0FF;
}
<my-navbar>
<ul class="bold">
<li>link1</li>
<li class="italic">link2</li>
<li>link3</li>
</ul>
<ul class="italic">
<li>link1</li>
<li class="bold">link2</li>
<li>link3</li>
</ul>
</my-navbar>
You will note that the background colors of the <ul>
and <li>
tags are set based on the external classes of bold
and italic
.
If you want to use a <slot>
the you agree that the developer using your component has the override power for anything that is placed into the slot.
If you don't want the user to have that kind of control then the only way to prevent it is to move the component's children into the component's shadow DOM.
But be careful when you do it.
According to the rules of Web Component constructors you can not access or change the children of a component while in the constructor.
But you have to remember that the connectedCallback
is called every time the component is inserted into the DOM. So if the developer removes and then re-appends your component then the connectedCallback
will be called again. So you have to add a gate to prevent it from getting called twice.
Also you might want to add a MutationObserver to see when the user changes the children of your components.