One of the main objectives of Web Components is to allow the creation of encapsulated custom elements. Encapsulated elements have their own styles that do not leak from or to their parent element. Polymer not only makes it easier to use the Shadow DOM specifications but also provides powerful polyfills to use Shadow DOM in most current browsers.
What is the Shadow DOM and Shady DOM in Polymer?
As discussed in the Web Component introduction, the Shadow DOM allows us to have encapsulated DOM sub-trees in the main DOM. This will create a scoped subtree in the DOM. This subtree will not leak it’s DOM or style to the outside DOM.
The Shadow DOM is already used by browsers to implement default HTML components such as the <video>
tag. The Shadow DOM specification allows us to use the same techniques in our own custom elements.
What is the Shady DOM and why do we need it?
Support for the Shadow DOM v1 specification is still not available on most browsers. Therefore, we need to use a polyfill to support Shadow DOM for unsupported browsers. Shadow DOM polyfills have already been created for this reason.
But, emulating the Shadow DOM can be expensive. Making it difficult to justify using Shadow DOM in production. This is where Shady DOM comes in.
Shady DOM offers similar behavior to the Shadow DOM without having to do major changes to the actual HTML structure of the page. This works by querying elements through the Shady DOMs APIs.
Due to the way that the Shady DOM works, it is a much faster Shadow DOM polyfill implementation. But, this comes at a cost.
- We need to view the DOM through the Shady DOM API
- Shady DOM is not a 1:1 implementation of the specification
- Shady DOM requires Shady CSS to support the style encapsulation and custom styling features of the shadow DOM. Shady CSS has some limitations of its own.
Shady DOM and Shady CSS are included in the webcomponents-lite.js
. This will be used as the polyfill for the Shadow DOM in this article.
Using Shadow DOM in Polymer using ‘dom-module’
Polymer allows for creating Shadow DOM declaratively. We’ve already done this when we were building a simple component with Polymer.
<dom-module id="my-element"> <template> <style>{ h1: { color: 'hot-pink' } }</style> <header> <h1>Custom Header</h1> </header> </template> </dom-module>
By using the <dom-module>
tag, we can get Polymer to attach a Shadow root, encapsulating the DOM and style inside that Shadow DOM.
Composing child items with Shadow DOM.
It is possible to have user-specified elements embedded in Polymer components. For example, we can have a button component that allows the users to use custom HTML for the content as the button. This makes elements even more reusable.
In Polymer, the content
tag was previously used for composing child elements. Since specification has not been widely accepted the slot
tag is being used in Polymer 2.
We can define one or multiple <slot>
elements in the component template. If using multiple slots, we need to name all but one slot.
<dom-module id="my-element"> <template> <header> <h1> <slot></slot> </h1> <slot name="footer"></slot> </header> </template> </dom-module>
In order to inject content into the slot
, we can simply add content within the custom component tags.
<my-element> Heading slot content <span slot="footer">footer content</span> </my-element>
Shadow DOM Styling
As we discussed, the Shadow DOM allows for encapsulating custom elements nicely. But, sometimes, we need to style these elements to fit our application. There are a few ways of styling these encapsulated elements.
Styling the host using :host, :host(), :host-context()
We sometimes need to style the custom component’s host container. In this example, it would be the <my-element>
element itself.
There are a few selectors made for this purpose. You may notice that these are the same selectors discussed in the angular component styling article.
- :host Selects the custom element tag
- :host(.class-name) Selects the custom element tag only when it has the specified class
- :host-context(.parent-class-name) Selects the custom element when its parents has the said styling. Useful for use cases such as validation highlighting.
Using custom CSS properties
We might not want to use hot-pink everywhere where we use the my-element
component. One way of styling this is to use CSS custom properties.
Custom properties are a CSS specification that allows for us to have reusable properties. We can define CSS variables by having double dashes ‘–’ followed by the variable name.
html { --theme-color: hot-pink; }
Then, in the component, we can use this variable value as a property as follows:
color: var(--theme-color);
Multiple properties using @apply
While custom properties allow us to set properties, the main limitation of this approach is that we need to specify exactly what we can and can’t style.
Custom property mixins can be used to have more complex CSS styling injected into the components.
The usage is similar to how we use custom properties. We define it using a similar syntax:
html { --my-mixin: { color: hot-pink; font-size: .5em; } }
Then in the components, we can use @apply
to apply the mixin into the component’s CSS.
:host { @apply --my-mixin; }
A common standard has not been agreed upon for custom element styling. There are multiple discontinued specifications such as /deep/, ::shadow and >>>. Even though we'll be using @apply with Polymer 2, this could be replaced in the future.