Recently I’m making a company-wide Web Component library. Here are some notes regarding styling. I’m going to use <l-header-bar>
component as an example.
Note 1: The Shadow DOM boundary (shadow root) prevents styling coming in or going out
Web components can isolate styles via the Shadow DOM, which is called scoped CSS:
- CSS selectors from the outer page don’t apply inside your component.
- Styles defined inside don’t bleed out. They’re scoped to the host element.
CSS selectors used inside shadow DOM apply locally to your component. In practice, this means we can use common id/class names again, without worrying about conflicts elsewhere on the page. Simpler CSS selectors are a best practice inside Shadow DOM. They’re also good for performance.
Meanwhile, outside CSS selectors are not able to access the HTML element inside Shadow DOM. The following code won’t work.
1 | <!-- l-header-bar template --> |
1 | <!-- user's HTML --> |
Note 2: Custom properties go through the shadow DOM
Styles inside shadow DOM can access CSS variables even from outside.
According to this, One way to custom style is to provide hooks in the form of CSS variables and let user use these variables.
For example, inside l-header-bar
:
1 | <!-- l-header-bar template --> |
In user’s HTML, they can do this.
1 | <!-- user's HTML --> |
Note 3: A slot is globally stylable
A slot element style is defined outside the Web Components.
1 | <!-- l-header-bar template --> |
In user’s code, they can completely control the action-left slot style(which is a customized button in the following case). ← This allows our components to be flexible.
1 | <!-- user's HTML --> |
Note 4: A slot can also take styles defined inside the shadow DOM.
Inside Shadow DOM, we can use ::slotted(<compound-selector>)
to style nodes that are distributed into a <slot>
. So this allows us to put additional styles to user’s slots. However, we can only select top-level slotted nodes.
1 | <!-- user's HTML --> |
1 | <!-- l-header-bar template --> |
Note 5: Inherited properties will be inherited as usual.
Take a look at the following code from the user side to see how the properties affect style inside l-header-bar
.
1 | <!-- user's HTML --> |
However, l-header-bar
can prevent external inheritable styles from affecting the interior, by setting all: initial;
.
1 | <!-- l-header-bar template --> |
Note 6: For styling the component itself (Shadow DOM host): Outside styles always win over styles defined within Shadow DOM
The user is free to style the component element itself because it’s just light dom.
Rules in the parent page have higher specificity than :host
rules defined in the element. That is, outside styles win. This allows users to override your top-level styling from the outside. Also, :host
only works in the context of a shadow root, so you can’t use it outside of shadow DOM.
1 | <!-- l-header-bar template --> |
1 | <!-- user's HTML --> |
Note 7: :host(<selector>)
and :host-context(<selector>)
1 | <!-- user's HTML --> |
1 | <!-- l-header-bar template --> |