This specification describes a method of combining multiple DOM trees into one hierarchy and how these trees interact with each other within a document, thus enabling better composition of the DOM.
All diagrams, examples, notes, are non-normative, as well as sections explicitly marked as non-normative. Everything else in this specification is normative.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in [[!RFC2119]]. For readability, these words do not appear in all uppercase letters in this specification.
To help with layering and to avoid circular dependencies between various parts of specification, this document consists of three consecutive narratives:
In a sense, these parts can be viewed as math, which sets up the reasoning environment, physics, which is the theoretical reasoning about the concept, and mechanics, which is the practical application of this reasoning.
Any point, at which a conforming UA must make decisions about the state or reaction to the state of the conceptual model, is captured as algorithm. The algorithms are defined in terms of processing equivalence. The processing equivalence is a constraint imposed on the algorithm implementors, requiring the output of the both UA-implemented and the specified algorithm to be exactly the same for all inputs.
A document tree is a node tree [[!DOM]] whose root node is a document.
Any element can host zero or one associated node trees, called a shadow tree.
A shadow host is an element that hosts one shadow tree.
A shadow tree has an associated flag, called an encapsulation mode, which is either open or closed.
A shadow root is the root node of a shadow tree.
A node A is called a composed child of a node B, if either A is a child of B or A is the root node of the shadow tree that B hosts.
A node A is called a composed descendant of a node B, if either A is a composed child of B or A is a composed child of a node C that is a composed descendant of B.
An inclusive composed descendant is a node or one of its composed descendants.
A node A is called a composed parent of a node B if and only if B is a composed child of A.
A node A is called a composed ancestor of a node B if and only if B is a composed descendant of A.
An inclusive composed ancestor is a node or one of its composed ancestors.
When a node is an inclusive composed descendant of the root element of a Document object, it is in a composed document.
A composed tree of node trees is a tree of node trees.
The purpose of introducing a composed tree of node trees here is to define algorithms easily in the following sections. This is a kind of a notation techchique to make the this specification simpler.
Just like a node tree is defined as a set of relationships between nodes, a composed tree of node trees is similarly defined as a set of relationships between node trees:
A node tree A is called an unclosed tree of a node tree B if and only if one, at least, of the following conditions is satisfied:
A node A is called an unclosed node of a node B if and only if the node tree that A participates in is an unclosed tree of the node tree that B participates in.
A composed document is a composed tree of node trees whose root tree is a document tree.
Window object named properties [[!HTML]] must access the nodes in the document tree.
In the figure, there are six node trees, named A, B, C, D, E and F.
The shadow trees, B, C and D, are hosted by elements which participate in the document tree A.
The shadow trees, E and F, are hosted by elements which participates in the shadow tree D.
The following set of relationships holds in the figure:
A's child trees is [B, C, D].B's child trees is [].C's child trees is [].D's child trees is [E, F].E's child trees is [].F's child trees is [].A, is the root tree of the composed tree of node trees.
As for a relationship between nodes, it's worth mentioning that there is no ancestor/descendant relationships between two nodes if they participate in different node trees.
A shadow root is not a child node of the shadow host. The parent node of a shadow root doesn't exist.
Because of this nature, most of existing APIs are scoped and don't affect other node trees, even though they are forming one composed tree of node trees.
For example, document.getElementById(elementId) never returns an element in a shadow tree,
even when the element has the given elementId.
The same thing also applies to CSS Selectors matching. For example, a descendant combinator never descends into a node in a child shadow tree because a shadow root is not a child node of the shadow host. Unless a special CSS Selector for Shadow DOM, which is mentioned later, is used, a CSS Selector never matches an element in a different node tree.
Because ShadowRoot inherits DocumentFragment, as specified later,
you can use ShadowRoot.getElementByID(elementId) to get a node in the shadow tree.
DocumentFragment, but which is not a shadow root, is the root tree of a composed tree of node trees.
A flat tree is a node tree which is constructed out of nodes from multiple node trees in a composed tree of node trees. The exact algorithm of constructing a flat tree is specified later.
A document flat tree is a flat tree whose root node is a document
A node is in a document flat tree if it participates in a document flat tree.
Unless an element is in a document flat tree, the element must not create any CSS box.
In resolving CSS inheritance, an element must inherit from the parent node in the flat tree, if applicable.
User agents must use the document flat tree in the visual formatting model, instead of the document tree.
The editor's draft of CSS Scoping specification [[css-scoping-1]] defines the selectors which are related to Shadow DOM. Specifically, it defines the following selectors related to Shadow DOM:
::shadow pseudo element/deep/ combinator, which was replaced with a >>> combinator (or shadow piercing descendant combinator)::content pseudo-element:host pseudo-class and :host() functional pseudo-class:host-context() functional pseudo-classThe slot element represents an instruction element, called slot. When constructing a flat tree, a slot does not participate in the flat tree. Instead, the slot cherry-picks its contents from other places in the composed tree of node trees. The exact algorithm of constructing a flat tree is specified later.
A slot usually cherry-picks its contents from the shadow host.
A node can be assigned to a slot, called an assigned slot. The exact algorithm of determining the assigned slot for a node is specified later.
A node is called slot assignable if it is either Text or Element.
A distribution is the mechanism that determines which nodes appear at each slot. The exact algorithm of a distribution is specified later.
A slot name is the name of a slot.
A default slot is the first slot element, in tree order, in a node tree, whose slot name is the empty string or missing.
The slotting algorithm must be used to determine the assigned slot of each node and must be equivalent to processing the following steps:
The get assigned nodes algorithm must be used to determine the assigned nodes of a slot and must be equivalent to processing the following steps:
The get distributed nodes algorithm must be used to determine the distributed nodes of a slot and must be equivalent to processing the following steps:
The flat tree children calculation algorithm must be used to determine the child nodes of a node in the flat tree and must be equivalent to processing the following steps:
For a given composed tree of node trees COMPOSED-TREE, the flat tree constructed from COMPOSED-TREE must be equivalent to the following tree:
Suppose that we have the following composed tree of node trees:
This composed tree of node trees is composed of the following 3 node trees, one document tree and two shadow trees:
| node tree | Root node is: | Hosted by: | Composed of: (in tree order) |
|---|---|---|---|
| document tree 1 | A | - | A, B, C, D, E, F, G, H, I |
| shadow tree 1 | J | C | J, K, L, M, N, O, P, Q |
| shadow tree 2 | R | N | R, S, T |
Suppose that an assigned slot of each node, if it exists, is:
Then, the assigned nodes and the distributed nodes of each slot will be:
| node tree | Root node is: | Hosted by: | Composed of: (in tree order) | Assigned slot of each node | Assigned nodes of each slot | Distributed nodes of each slot |
|---|---|---|---|---|---|---|
| document tree 1 | A | - | A, B, C, D, E, F, G, H, I |
|
- | - |
| shadow tree 1 | J | C | J, K, L, M, N, O, P, Q |
|
|
|
| shadow tree 2 | R | N | R, S, T | - |
|
|
The document flat tree will be:
body element because a document flat tree is used in rendering.In each algorithm in this section, the Window must be considered as if it were the parent node of the Document so that the Window also receives an event.
A trusted event's scoped flag must be initialized to true
if the event is one of the following events: abort, error, select, change, load, loadedmetadata, reset,
resize, scroll and selectstart.
A trusted event's relatedTargetScoped flag must be initialized to true
if the event has a relatedTarget property [[!DOM-Level-3-Events]].
The event path calculation algorithm must be used to determine event path and must be equivalent to processing the following steps:
relatedTargetScoped flag is set and EVENT has a relatedTarget property:
The composed event path calculation algorithm must be equivalent to processing the following steps:
The event path trimming algorithm must be equivalent to processing the following steps:
The definitions of relative target and relative related target are specified later.
The motivation of the event path trimming algorithm is to avoid the appearance of spurious events, such as mouseover or mouseout events, firing at the node in ancestor trees
in cases where both relatedTarget and target are part of the same shadow tree,
Let's re-use the same composed tree of node trees used in the flattening example section. Suppose that an event is dispatched on node I. The event path will be:
[I, H, O, T, S, R, N, J, C, A] (For the purpose of the explanation, Window is not shown here. The actual event path contains Window.)
It's worth pointing out that if we exclude all nodes which don't participate in the flat tree from the event path,
the result would be equivalent to the inclusive ancestors of the node I in the flat tree.
I, in the flat tree.
Note that the event path calculation algorithm is designed to achieve the following goals:
A local event path for each node tree would be seen as:
| node tree | Local Event Path |
|---|---|
| document tree 1 | [I, H, C, A] |
| shadow tree 1 | [O, N, J] |
| shadow tree 2 | [T, S, R] |
That means, if your concern is only one node tree, you can forget all other node trees. The event path would be seen as if the event happened only on the node tree you are focusing on. This is an important aspect in a sense that hosting a shadow tree doesn't have any effect to the local event path as long as the event is not stopped somewhere in the descendant trees.
If you are a web author and your concern is only a document tree, this might be a good news because an event listener that is registered somewhere on the document tree would continue to work even when you attach a shadow root to an element in the document tree to enhance the element. At the same time, an author of a shadow tree also can receive an event which will happen on a node in the document tree, if the node, or its ancestor, is assigned to a slot in the shadow tree.
target RetargetingThe value of the Event object's target attribute must be the result of the retargeting algorithm with the event's currentTarget and original target value, before adjusted, as input. The result is called a relative target.
The retargeting algorithm must be equivalent to processing the following steps:
Event retargeting is a process of computing relative targets for each ancestor of the node at which the event is dispatched. The event target retargeting process must occur prior to dispatch of an event. In other words, any DOM mutation occurred in an event listener does not have any affect on the result of retargeting process.
The motivation of retargeting is to maintain an encapsulation in the cases where event path is across multiple node trees.
The event's target might not be an unclosed node at some of nodes in the event path without retargeting.
A relative target is a unclosed node that most accurately represents the target of a dispatched event at each node in the event path.
relatedTarget RetargetingSome events have a relatedTarget property, which holds a node that's not the event's target, but is related to the event.
The value of the Event object's relatedTarget attribute must be the result of the retargeting algorithm with the event's currentTarget and relatedTarget as input.
The result is called a relative related target.
The event relatedTarget retargeting process must occur prior to dispatch of an event.
The Touch target [[!TOUCH-EVENTS]] attribute must be adjusted in the same way as an event with a relatedTarget. Each Touch target in the TouchList returned from TouchEvent touches(), changedTouches() and targetTouches() must be the result of the retargeting algorithm with a current target and Touch target as input.
Suppose we have a user interface for a media controller, represented by this tree, composed of both document tree and the shadow trees. In this example, we will assume that selectors are allowed to cross the shadow boundaries and we will use these selectors to identify the elements. Also, we will invent a fictional shadow-root element to demarcate the shadow boundaries and represent shadow roots:
<div id="player">
<shadow-root id="player-shadow-root">
<div id="controls">
<button id="play-button">PLAY</button>
<input type="range" id="timeline">
<shadow-root id="timeline-shadow-root">
<div id="slider-thumb" id="timeline-slider-thumb"></div>
</shadow-root>
</input>
<div id="volume-slider-container">
<input type="range" id="volume-slider">
<shadow-root id="volume-shadow-root">
<div id="slider-thumb" id="volume-slider-thumb"></div>
</shadow-root>
</input>
</div>
</div>
</shadow-root>
</div>
Let's have a user position their pointing device over the volume slider's thumb (#volume-slider-thumb), thus triggering a mouseover event on that node. For this event, let's pretend it has no associated relatedTarget.
Per the retargeting algorithm, we should have the following set of ancestors and relative targets:
| Ancestor | Relative Target |
|---|---|
#player |
#player |
#player-shadow-root |
#volume-slider |
#controls |
#volume-slider |
#volume-slider-container |
#volume-slider |
#volume-slider |
#volume-slider |
#volume-shadow-root |
#volume-slider-thumb |
#volume-slider-thumb |
#volume-slider-thumb |
After we dispatch the mouseover event using these newly computed relative targets, the user decides to move their pointing device over the thumb of the timeline
(#timeline-slider-thumb). This triggers both a mouseout event for the volume slider thumb and the mouseover event for the timeline thumb.
Let's see how the relatedTarget value of the volume thumb's mouseout event is affected. For this event, the relatedTarget is the timeline thumb (#timeline-slider-thumb). Per the relatedTarget retargeting, we should have the following set of ancestors and adjusted related targets:
| Ancestor | Relative Target | Relative Related Target |
|---|---|---|
#player |
#player |
#player |
#player-shadow-root |
#volume-slider |
#timeline |
#controls |
#volume-slider |
#timeline |
#volume-slider-container |
#volume-slider |
#timeline |
#volume-slider |
#volume-slider |
#timeline |
#volume-shadow-root |
#volume-slider-thumb |
#timeline |
#volume-slider-thumb |
#volume-slider-thumb |
#timeline |
The node, #player, has both target and relatedTarget being the same value (#player), which means that we do not dispatch the event on this node and its ancestors.
At the time of event dispatch:
MouseEvent offsetX and offsetY attributes must return the coordinates relative to the origin of the padding edge of the relative targetEvent eventPhase attribute must return AT_TARGET if the relative target is same as the node on which event listeners are invokedbubbles attribute value is false, run these substeps:
eventPhase attribute to AT_TARGETUpon completion of the event dispatch, the Event object's target and relatedTarget must be to the highest ancestor's relative target and relative related target, respectively. Since it is possible for a script to hold on to the Event object past the scope of event dispatch, this step is necessary to avoid revealing the nodes in shadow trees.
The mutation event types must never be dispatched in a shadow tree.
Selection [[!EDITING]] is not defined. Implementation should do their best to do what's best for them. Here's one possible, admittedly naive way:
Since nodes which are in the different node trees never have the same root, there may never exist a valid DOM range that spans multiple node trees.
Accordingly, selections may only exist within one node tree, because they are defined by a single range. The selection, returned by the window.getSelection() method never returns a selection within a shadow tree.
The getSelection() method of the shadow root object returns the current selection in this shadow tree.
If a node doesn’t participate in the document flat tree, the node must be skipped from the sequential focus navigation.
The sequential focus navigation order for a given shadow tree A must be inserted into the sequential focus navigation order for the parent tree B as follows:
For directional focus navigation [[!CSS3-UI]], it is up to the user agent to integrate the shadow trees into the document's directional focus navigation.
To maintain encapsulation, the value of the Document object's focus API property activeElement must be adjusted. To prevent loss of information when adjusting this value, each shadow root must also have an activeElement property to store the value of the focused element in the shadow tree.
The active element adjustment algorithm must be used to determine the value of activeElement property, and it must be equivalent to processing the following steps:
The value of the contenteditable attribute must not propagate from shadow host to its shadow trees.
User agents with assistive technology traverse the flat tree, and thus enable full use of WAI-ARIA [[!WAI-ARIA]] semantics in the shadow trees.
When a text node is a child node of a shadow root, a hit testing must target the shadow host if the text node is the result of the hit testing.
User-agent mouse events must be targeted to the parent node in the flat tree of a text node if the topmost event target is the text node.
This section eventually needs to be part of some general hit testing specification.
Comparatively, a shadow tree can be seen as somewhere between just part of a document and itself being a document fragment. Since it is rendered, a shadow tree aims to retain the traits of a typical tree in a document. At the same time, it is an encapsulation abstraction, so it has to avoid affecting the document tree. Thus, the HTML elements must behave as specified [[!HTML]] in the shadow trees, with a few exceptions.
According to the [[!HTML]], some HTML Elements would have different behavior if they participate in a shadow tree, instead of a document tree, because their definitions require the elements to be in a document as a necessary condition for them to work. In other words, they shouldn't work if they participate in a shadow tree, even when they are in a composed document. We must fill this gap because we expect that most of HTML Elements behave in the same way as in a document, as long as they are in a composed document. See W3C Bug 26365 and Bug 27406 for the details. The following is the tentative summary of the discussions in the W3C bugs. We, however, haven't covered all HTML Elements and their behaviors here yet. For HTML Elements which are not explicitly stated here, they should be considered as active in a shadow tree. We are trying to update [[!HTML]] itself, instead of having monkey patches here.
HTML Elements are classified into the following categories:
Active in a shadow tree
A subset of HTML elements which must behave as if they were in the document tree, even when they participate in a shadow tree, as long as they are in a composed document.
The following HTML elements must be classified to this category:
dialogiframestyleInert in a shadow tree:
A subset of HTML elements which must behave as inert, or not part of the document tree, if they participate in a shadow tree. This is consistent how the HTML elements would behave in a document fragment.
The following HTML elements must be classified to this category:
Inert unless being rendered:
A subset of HTML elements which must behave as inert, or not part of the document tree, unless they are being rendered. In other words, if they don't particitpate in a document flat tree, they must behave as inert.
The following HTML elements must be classified to this category:
appletembedobject
For example, suppose that an object element is a child node of a shadow host, but the object element is not assigned to a slot.
In this case, according to the flat tree children calculation algorithm,
this element never participate in a document flat tree.
Therefore, this element is inert because this element is not being rendered. .
When [[!HTML]] defines the processing algorithms to traverse trees for the following attributes, they must use the flat tree.
dirdraggabledropzonehiddenlang and xml:langspellchecktitleThis list does not include attributes that are defined elsewhere in this specification. Such attributes include:
tabindex is defined in Focus Navigation.role and ARIA are defined in Assistive Technology.ShadowRoot interfaceThe ShadowRoot interface represents the shadow root.
Returns the current selection in the shadow tree.
When invoked, it must return the selection in the shadow tree.
Returns an element at specified coordinates.
Eventually, this needs to be part of CSSOM View Module specification [[!CSSOM-VIEW]]. See also W3C bug 27829.
When invoked, it must return result of running the following steps:
ShadowRoot instance, throw an InvalidNodeTypeError.x is greater than the viewport width excluding the size of a rendered scroll bar (if any), or if y is greater than the viewport height excluding the size of a rendered scroll bar (if any), return null.x and y in the viewport, determined through hit testingIt could be defined in roughly the same way as elementFromPoint. Eventually, this needs to be part of CSSOM View Module specification [[!CSSOM-VIEW]]. See also W3C bug 27829.
It could be defined in roughly the same way as caretPositionFromPoint [[!CSSOM-VIEW]]. Eventually, this needs to be part of CSSOM View Module specification [[!CSSOM-VIEW]]. See also W3C bug 27829.
Represents the currently focused element in the shadow tree.
On getting, the attribute must return the currently focused element in the shadow tree or null, if there is none.
Represents the shadow host which hosts the context object.
On getting, the attribute must return the shadow host which hosts the context object.
Represents the markup of ShadowRoot's contents.
On getting, the attribute must return the result of running the HTML fragment serialization algorithm with the context object as shadow host.
On setting, these steps must be run:
shadow hostRepresents the style sheets in the shadow tree.
This is defined in the similar way as defined in Document's styleSheets. On getting, the attribute must return a StyleSheetList sequence containing the style sheets in the shadow tree.
The nodeType attribute of a ShadowRoot instance must return DOCUMENT_FRAGMENT_NODE. Accordingly, the nodeName attribute of a ShadowRoot instance must return "#document-fragment".
Invoking the cloneNode() method on a ShadowRoot instance must always throw a DATA_CLONE_ERR exception.
Element InterfaceNotSupportedError exception.
articleasideblockquotebodydivfooterh1h2h3h4h5h6headernavpsectionspanThis list is possible to change. See Issue #110.
InvalidStateError exception.ShadowRoot object.
The shadowRootInitDict argument allows for setting the encapsulation mode.
ShadowRoot object.ShadowRoot object.Represents the assigned slot of the context object.
On getting, the attribute must return the assigned slot of the context object, if there is, and the assigned slot participates in an open shadow tree.
Otherwise must return null.
Reflects the slot attribute. The slot attribute represents the slot name of a slot to where this element is assigned.
Represents the shadow root that context object hosts.
On getting, the attribute must return the shadow root that context object hosts if there is and it is open. Otherwise must return null.
ShadowRootInit dictionaryShadowRootMode enumText InterfaceRepresents the assigned slot of the context object.
On getting, the attribute must return the assigned slot of the context object, if there is, and the assigned slot participates in an open shadow tree.
Otherwise must return null.
slot elementThe slot element is used to define a location of a slot.
name, the slot nameflatten is true, the distributed nodes must be returned.
Otherwise, the assigned nodes must be returned.
AssignedNodesOptions dictionarySpecifies whether getAssignedNodes() returns assigned nodes or distributed nodes
EventInit DictionarySpecifies the scoped flag of Event
Specifies the relatedTargetScoped flag of Event
Event InterfaceWhen invoked, it must return return a sequence consisting of event targets, that must be equivalent to processing the following steps:
Returns the scoped flag.
Returns the relatedTargetScoped flag.
Bob was asked to turn a simple list of links into a News Widget, which has links organized into two categories: breaking news and just news. The current document markup for the stories looks like this:
<ul class="stories">
<li><a href="//example.com/stories/1">A story</a></li>
<li><a href="//example.com/stories/2">Another story</a></li>
<li class="breaking" slot="breaking"><a href="//example.com/stories/3">Also a story</a></li>
<li><a href="//example.com/stories/4">Yet another story</a></li>
<li><a href="//example.com/stories/5">Awesome story</a></li>
<li class="breaking" slot="breaking"><a href="//example.com/stories/6">Horrible story</a></li>
</ul>
It's weird that there are slot attributes in this markup because Bob has not decided to use Shadow DOM yet.
To organize the stories, Bob decides to use shadow DOM. Doing so will allow Bob to keep the document markup uncluttered, and harnessing the power of insertion point makes sorting stories by class name a very simple task. After getting another cup of Green Eye, he quickly mocks up the following shadow tree, to be hosted by the ul element:
<div class="breaking">
<ul>
<slot name="breaking"></slot> <!-- slot for breaking news -->
</ul>
</div>
<div class="other">
<ul>
<slot></slot> <!-- slot for the rest of the news -->
</ul>
</div>
Bob then styles the newborn widget according to comps from the designer by adding this to the shadow tree mockup:
<style>
div.breaking {
color: Red;
font-size: 20px;
border: 1px dashed Purple;
}
div.other {
padding: 2px 0 0 0;
border: 1px solid Cyan;
}
</style>
While pondering if his company should start looking for a new designer, Bob converts the mockup to code:
function createStoryGroup(className, slotName)
{
var group = document.createElement('div');
group.className = className;
// Empty string in slot name attribute or absence thereof work the same, so no need for special handling.
group.innerHTML = '<ul><slot name="' + slotName + '"></slot></ul>';
return group;
}
function createStyle()
{
var style = document.createElement('style');
style.textContent = 'div.breaking { color: Red;font-size: 20px; border: 1px dashed Purple; }' +
'div.other { padding: 2px 0 0 0; border: 1px solid Cyan; }';
return style;
}
function makeShadowTree(storyList)
{
var root = storyList.attachShadow({mode: 'open'});
root.appendChild(createStyle());
root.appendChild(createStoryGroup('breaking', 'breaking'));
root.appendChild(createStoryGroup('other', ''));
}
document.addEventListener('DOMContentLoaded', function() {
[].forEach.call(document.querySelectorAll('ul.stories'), makeShadowTree);
});
Well done, Bob! With the cup of coffee still half-full, the work is complete. Recognizing his awesomeness, Bob returns to teaching n00bs the ways of Splatoon.
David Hyatt developed XBL 1.0, and Ian Hickson co-wrote XBL 2.0. These documents provided tremendous insight into the problem of functional encapsulation and greatly influenced this specification.
Alex Russell and his considerable forethought triggered a new wave of enthusiasm around the subject of shadow DOM and how it can be applied practically on the Web.
Dominic Cooney, Hajime Morrita, and Roland Steiner worked tirelessly to scope the problem of functional encapsulation within the confines of the Web platform and provided a solid foundation for this document.
The editor would also like to thank Alex Komoroske, Anne van Kesteren, Brandon Payton, Brian Kardell, Darin Fisher, Eric Bidelman, Deepak Sherveghar, Edward O'Connor, Elisée Maurer, Elliott Sprehn, Erik Arvidsson, Glenn Adams, Jonas Sicking, Koji Ishii, Malte Ubl, Mike Taylor, Oliver Nightingale, Olli Pettay, Rafael Weinstein, Richard Bradshaw, Ruud Steltenpool, Sam Dutton, Sergey G. Grekhov, Shinya Kawanaka, Tab Atkins, Takashi Sakamoto, and Yoshinori Sano for their comments and contributions to this specification.
This list is too short. There's a lot of work left to do. Please contribute by reviewing and filing bugs—and don't forget to ask the editor to add your name into this section.