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.
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.
Right now the logic goes like: if you're slotted, traverse your slots and collect rules in their shadow trees as needed. This is the code This is nice because the complexity of styling the element depends directly on the complexity of the shadow trees that you're building, and it only affects slotted nodes. The shadow DOM specification includes a means for allowing content from outside the shadow root to be rendered inside of our custom element. For those of you who remember AngularJS, this is a similar concept to ng-transclude or using props.children in React. With Web Components, this is done using the element. Shadow DOM provides a way to scope CSS styles to a specific DOM subtree and isolate that subtree from the rest of the document. The element gives us a way to control where the children of a Custom Element should be inserted within its Shadow Tree. 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.
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.
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.
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:
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.
Shadow DOM provides a few new APIs for checking distribution:
HTMLElement.assignedSlot
property gives the assigned slot for an element, or null
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.For more details, see Working with slots in JS on Web Fundamentals.
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.
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:
<img>
as the target.<fancy-button>
receives the <fancy-button>
as the target, because theoriginal target is inside its shadow root.<div>
in <example-card>
's shadow DOM also receives <fancy-button>
as thetarget, since they are in the same shadow DOM tree.<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:
<img>
element itself.<fancy-button>
.<div>
element.<example-card>
.<example-card>
(for example, <body>
, <html>
, document
and Window
).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.
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.
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.
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:
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.
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.
The polyfills use a combination of techniques to emulate shadow DOM:
The following sections discuss each polyfill in more depth.
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.
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.
For further reading: