Slots Shadow Dom
Shadow DOM is a new DOM feature that helps you build components. You can think of shadow DOM as ascoped subtree inside your element.
Read more on Web Fundamentals. This document gives an overview of shadow DOM as it relates toPolymer. For a more comprehensive overview of Shadow DOM, seeShadow DOM v1: self-contained web componentson Web Fundamentals.
- Since Angular 7, we can use slots to project markup into an component's template. These slots have been introduced with Shadow DOM v1 which Angular supports since version 6.1. This article shows how to use both, Shadow DOM v1 and slots. For this, it uses a component that shows a diagram.
- Within the shadow DOM, specifies the insertion point (where slot='X' elements are rendered). Afterward, composition is implemented by the browser. The composition takes elements from the light DOM rendering them in the matching slots of the shadow DOM. At the end, there is a component, which should be fulfilled with data.
Within the shadow DOM, slot name='X' specifies the insertion point (where slot='X' elements are rendered). Afterward, composition is implemented by the browser. The composition takes elements from the light DOM rendering them in the matching slots of the shadow DOM. At the end, there is a component, which should be fulfilled with data.
Consider a header component that includes a page title and a menu button: The DOM tree for thiselement might look like this:
Shadow DOM lets you place the children in a scoped subtree, so document-level CSS can't restyle thebutton by accident, for example. This subtree is called a shadow tree.
The shadow root is the top of the shadow tree. The element that the tree is attached to(<my-header>
) is called the shadow host. The host has a property called shadowRoot
thatrefers to the shadow root. The shadow root has a host
property that identifies its host element.
The shadow tree is separate from the element's children
. You can think of this shadow tree as partof the component's implementation, which outside elements don't need to know about. Theelement's children are part of its public interface.
You can add a shadow tree to an element imperatively by calling attachShadow
:
Polymer provides a declarative mechanism for adding a shadow tree using a DOM template.When you provide a DOM template for an element, Polymer attaches a shadow root for each instance ofthe element and copies the template contents into the shadow tree.
Note that the template includes a <style>
element. CSS placed in the shadow tree is scoped to theshadow tree, and won't leak out to other parts of your DOM.
Shadow DOM and composition
By default, if an element has shadow DOM, the shadow tree is rendered instead of the element'schildren. To allow children to render, you can add a <slot>
element to your shadow tree. Thinkof the <slot>
as a placeholder showing where child nodes will render. Consider the followingshadow tree for <my-header>
:
The user can add children like this:
The header renders as if the <slot>
element was replaced by the children:
The element's actual descendant tree is sometime called its light DOM, in contrast to its shadow DOMtree.
The process of turning the light DOM and shadow DOM trees into a single tree for rendering is calledflattening the tree. While the <slot>
elements don't render, they are included in theflattened tree, so they can take part in event bubbling, for example.
You can control where a child should be distributed into the flattened tree using named slots.
A named slot only accepts top-level children that have a matching slot
attribute:
A slot with no name
attribute acts as the default slot for any children that don't have a slot
attribute. If a child's slot
attribute doesn't match any named slot element in the shadow tree,that child doesn't appear at all.
For example, consider an example-card
element with the following shadow tree:
If the example card is used like this:
The first span is assigned into the title
slot. The div, which has no slot
attribute, isassigned to the default slot. The final span, which has a slot name that doesn't appear in theshadow tree, doesn't show up in the flattened tree, and doesn't render.
Note that only top-level children can match a slot. Consider the following example:
The <example-card>
has two top-level children, both <div>
elements. Both are assigned to thedefault slot. The slot
attribute on the span has no effect on the distribution, because the spanisn't a top-level child.
Fallback content
A slot can contain fallback content that's displayed when no nodes are assigned to the slot. Forexample:
The user can supply their own icon for the
If the user omits the icon, the fallback content supplies a default icon:
Multi-level distribution
A slot element may also be assigned to a slot. For example, consider two levels of shadow trees.
Given markup like this:
The flattened tree looks like this:
The ordering may be a little confusing at first. At each level, the light DOM children areassigned to a slot in the host's shadow DOM. The span 'I'm in light DOM' is assigned to theslot #parent-slot
in <parent-element>
's shadow DOM. The #parent-slot
is then assigned to#child-slot
in <child-element>
's shadow DOM.
Note: This example uses id
on slots for illustration purposes only. This is not the same asthe name
attribute. These slots are unnamed and are therefore default slots.
The slot elements don't render, so the rendered tree is much simpler:
In spec language, a slot's distributed nodes are the assigned nodes, with any slots replaced bytheir assigned nodes or fallback content. So in the example above, #child-slot
has onedistributed node, the span. You can think of the distributed nodes as the list of nodes that takethe place of the slot in the rendered tree.
Slot APIs
Shadow DOM provides a few new APIs for checking distribution:
HTMLElement.assignedSlot
property gives the assigned slot for an element, ornull
if theelement isn't assigned to a slot.HTMLSlotElement.assignedNodes
method returns the list of nodes associated with a given slot.When called with the{flatten: true}
option, returns the distributed nodes for a slot.- HTMLSlotElement.slotchange event is fired when a slot's distributed nodes change.
For more details, see Working with slots in JS on Web Fundamentals.
Observe added and removed children
The Polymer.FlattenedNodesObserver
class provides utilities to track an element's flattened tree.That is, a list of the node's child nodes, with any <slot>
elements replaced by their distributednodes. FlattenedNodesObserver
is an optional utility that can be loaded fromlib/utils/flattened-nodes-observer.html
.
Polymer.FlattenedNodesObserver.getFlattenedNodes(node)
returns a list of flattened nodes forthe specified node.
Use the Polymer.FlattenedNodesObserver
class to track when the flattened node list changes.
You pass the FlattenedNodesObserver
a callback to be invoked when nodes are added orremoved. The callback takes a single Object argument, with addedNodes
andremovedNodes
arrays.
The method returns a handle that can be used to stop observation:
A few notes on FlattenedNodesObserver
:
The callback argument lists added and removed nodes, not just elements.If you're only interested in elements, you can filter the node list:
The observer handle also provides a
flush
method, that can be used for unit testing.
Event retargeting
To preserve the encapsulation of the shadow tree, some events are stopped at the shadow DOM boundary.
Other bubbling events are retargeted as they bubble up the tree. Retargeting adjusts the event'starget so that it represents an element in the same scope as the listening element.
For example, given a tree like this:
If the user clicks on the image element the click event bubbles up the tree:
- A listener on the image element itself receives the
<img>
as the target. - A listener on the
<fancy-button>
receives the<fancy-button>
as the target, because theoriginal target is inside its shadow root. - A listener on the
<div>
in<example-card>
's shadow DOM also receives<fancy-button>
as thetarget, since they are in the same shadow DOM tree. - A listener on the
<example-card>
receives the<example-card>
itself as the target.
The event provides a composedPath
method that returns an array of nodes that the event will passthrough. In this case, the array would include:
- The
<img>
element itself. - The shadow root of
<fancy-button>
. - The
<div>
element. - The shadow root of
<example-card>
. - Any ancestors of
<example-card>
(for example,<body>
,<html>
,document
andWindow
).
By default, custom events don't propagate though shadow DOM boundaries. To allow a custom eventto travel through a shadow DOM boundary and be retargeted, you need to create it with the composed
flag set to true
:
For more information on events in shadow trees, see The Shadow DOM event model in the Web Fundamentals article on shadow DOM.
Shadow DOM styling
Styles inside a shadow tree are scoped to the shadow tree, and don't affect elements outside theshadow tree. Styles outside the shadow tree also don't match selectors inside the shadow tree.However, inheritable style properties like color
still inherit down from host to shadow tree.
In this example, the <div>
has a blue background, even though the div
selector is less specificthan the .test
selector in the main document. That's because the main document selector doesn'tmatch the <div>
in the shadow DOM at all. On the other hand, the white text color set on thedocument body inherits down to <styled-element>
and into its shadow root.
There is one case where a style rule inside a shadow tree matches an element outside the shadow tree.You can define styles for the host element, using the :host
pseudoclass or the :host()
functional pseudoclass.
You can also style light DOM children that are assigned to slots using the ::slotted()
pseudoelement. For example, ::slotted(img)
selects any image tags that are assigned to slots in theshadow tree.
For more information, see Styling in the Web Fundamentals article on shadow DOM.
Theming and custom properties
You can't directly style anything in a shadow tree using a CSS rule outside of the shadow tree.The exception is CSS properties (such as color and font) that inherit down the tree. A shadow treeinherits CSS properties from its host.
To let users customize your element, you can expose specific styling properties using CSS customproperties and custom property mixins. Custom properties provide a way to add a styling API to yourelement.
Polyfill limitations. When using polyfilled versions of custom properties and mixins, there area number of limitations you should be aware of. For details, see the Shady CSS README file.
Slots Shadow Domain
You can think of a custom property as a variable that can be substituted in to your CSS rules:
This sets the host's background color to the value of the --my-theme-color
custom property. Anyoneusing your element can set the property at a higher level:
Custom properties inherit down the tree, so a value set at the document level is accessible frominside a shadow tree.
The substitution can include default values to use if no property is set:
The default can even be another var()
function:
Slots Without Shadow Dom
Custom property mixins
Custom property mixins are a feature built on top of the custom property specification. Basically,the mixin is a variable that contains multiple properties:
A component can import or mix in the entire set of rules using the @apply
rule:
The @apply
rule has the same effect as adding the contents of --my-custom-mixin
inline in theruleset where @apply
is used.
Shadow DOM polyfills
Because shadow DOM is not available on all platforms, Polymer takes advantage of the shady DOM andshady CSS polyfills if they're installed. These polyfills are included in the webcomponents-lite.js
polyfill bundle.
These polyfills provide reasonable emulation of native shadow DOM while maintaining good performance.However, there are some shadow DOM features that can't be polyfilled completely. If you'resupporting browsers that don't include native shadow DOM, you need to be aware of these limitations.It's also helpful to understand some details of the shady DOM polyfill when debugging applicationsunder shady DOM.
How the polyfills work
The polyfills use a combination of techniques to emulate shadow DOM:
- Shady DOM. Maintains the logical divisions of shadow tree and descendant tree internally, sochildren added to the light DOM or shadow DOM render correctly. Patches DOM APIs on affectedelements in order to emulate the native shadow DOM APIs.
- Shady CSS. Provides style encapsulation by adding classes to shadow DOM children and rewritingstyle rules so that they apply to the correct scope.
The following sections discuss each polyfill in more depth.
Shady DOM polyfill
A browser without native shadow DOM only renders the document and its tree of descendants.
To emulate shadow DOM's rendering of the flattened tree, the shady DOM polyfill has to maintainvirtual children
and shadowRoot
properties with separate logical trees. Each host element'sactual children
—the descendant tree visible to the browser—is a pre-flattened tree of shadow andlight DOM children. The tree you'll see using developer tools looks like the rendered tree, not thelogical tree.
Under the polyfill, the slot elements don't appear in the browser's view of the tree. So unlikenative shadow DOM, slots don't take part in event bubbling.
The polyfill patches existing DOM APIs on nodes that are affected by shadow DOM—that is, nodesare in a shadow tree, nodes that hose a shadow tree, or nodes that are light DOM children of shadowhosts. For example, when you call appendChild
on a node with a shadow root, the polyfill adds thechild to a virtual tree of light DOM children, calculates where the child should appear in therendered tree, and then adds it to the actual descendant tree in the correct place.
For more information, see the Shady DOM polyfill README.
Shady CSS polyfill
The Shady CSS polyfill emulates shadow DOM style encapsulation, and also provides emulation for CSScustom properties and custom property mixins.
To emulate encapsulation, the shady CSS polyfill adds classes to elements inside a shady DOM tree.It also rewrites style rules defined inside an element's template so that they're confined to theelement.
Shady CSS does not rewrite style rules in document-level stylesheets. This means that document-levelstyles can leak into shadow trees. However, it provides a custom element, <custom-style>
forwriting polyfilled styles outside of an element. This includes support for custom CSS properties andrewriting rules so they don't leak into shadow trees.
For more information, see the Shady CSS polyfill README.
Resources
For further reading:
- Shadow DOM v1: self-contained web components on Web Fundamentals.
- Custom properties specification.
- Custom property mixins proposal.
- Shady DOM polyfill README.
- Shady CSS polyfill README.
Web Components provide a component model to the Web. Web Components, instead of being a single spec, is a collection of several stand-alone Web technologies. Often Web Components will leverage Shadow DOM features. Shadow DOM is commonly used for CSS encapsulation. However, Shadow DOM has another useful feature called Slots.
The Slot API is a content projection API that allows HTML content from the host application to be rendered into your component template. Common examples of this are things like cards and modals.
Here is a minimal example of a Custom Element using the Slot API.
The tags’ content can be rendered into our template we defined. The browser render the content wherever the <slot>
element is placed. If we look at what the browser renders, we will see something like this:
The content is projected and rendered within the template of our component. Often there are use cases, whereas the component author we would like to know about any updates to the content provided by the slot element. We can achieve this by adding an event listener in our component for the slotchange
event.
This event will fire whenever any content has changed within the slot. To test this, we can use our component and dynamically update the content to see the event update.
In this example, every one second, we can set the textContent
or the innerHTML
of the component and see the slotchange
event fire within the x-component
.
We can easily render content into our component templates and listen for content updates. But there is one interesting exception to this rule. While the event will happen whenever textContent
or innerHTML
are set, the event will not occur if a textNode
reference is updated dynamically. Let’s take a look at an example.
Instead of directly setting the textContent
or innerHTML
of our element we create a text node. While not an HTML element, the text node allows us to hold a reference in memory we can update at a later point. So if we go back to our interval, we will see the text change, but the event is no longer triggered.
This behavior can be a bit unexpected at first. Many JavaScript frameworks will leverage text nodes to optimize for performance. The short rule to remember is slotchange
only fires when the HTML DOM has changed either by a DOM/Text Node from being added or removed. Check out the full working example below!