About this Document
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:
- setting up the stage for the specification,
- explaining of the conceptual model and algorithms behind it, and
- expressing this model with DOM interfaces and HTML elements.
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 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.
Dependencies
This document relies on the following specifications:
Terminology
The following terms are used throughout the specification:
- DOM
- The document object model
- document
- The Document Object Model's underlying document
- node
- Any DOM object that participates in a tree
- element
- A DOM object that implements the Element interface
- DOM tree
- Any tree, composed of DOM objects
- DOM structure
- A DOM tree or fragment of a DOM tree
Introduction
Web application developers often encounter the need to provide encapsulation of a DOM structure. Despite being part of one document tree, there are typically many functional fragments of DOM (or DOM subtrees), as well as assumptions about these fragments operating independently. This type of encapsulation is called functional encapsulation, as opposed to trust encapsulation, which deals with limiting information flow based on trust and ensuring security of data and state within an application.
Functional encapsulation is primarily concerned with establishing functional boundaries in a document tree. A functional boundary (or just boundary hereon) is a delineation of functional concerns between two loosely coupled units of functionality.
Functional Encapsulation Example
A Web application user interface is commonly composed of several user interface elements (or widgets), each a DOM subtree. In cases where a widget is tasked with hosting other widgets, the need arises for the widget to understand where its DOM subtree ends and another widget's DOM subtree begins.
This need for observing the functional boundaries in a document tree is even larger when a widget is operated on—added, moved, or removed in the document tree—by an outside actor, such as the Web application that consumes these widgets. Unless the widget consumer knows exactly how a widget's DOM structure is designed, it is impossible for the consumer to reasonably operate on the widget. A typical workaround has been providing alternative means of operation by the widget developer, which, in striving for API consistency quickly extrapolates into a complete set of widget-specific, DOM-like APIs.
Shadow DOM Subtrees
To solve this problem at its core, a new abstraction is introduced. The shadow DOM allows multiple DOM subtrees (in addition to the document tree) to be composed into one larger tree when rendered. The existence of multiple DOM subtrees is enabled by letting any element in the document tree to host one or more additional DOM subtrees. These shadow DOM subtrees are governed by a set of rules that establish encapsulation boundaries while retaining the standard DOM composability semantics.
The encapsulation boundaries between shadow DOM subtrees are called shadow boundaries. The elements that host shadow DOM subtrees are called shadow hosts, and the root nodes of the shadow DOM subtrees are called shadow roots.
When rendered, the shadow DOM subtree takes place of the host element's content.
To enable composition of host element's children and the shadow DOM subtree, a notion of insertion points is added to the abstraction. An insertion point is a defined location in the shadow DOM subtree, to which the host element's children are transposed when rendering.
Thus, the encapsulation of a shadow DOM subtree can be viewed as a two-fold problem:
- the upper-boundary encapsulation, or governing the boundary between the shadow root and the shadow host; and
- the lower-boundary encapsulation, or governing the boundary between the insertion points and the shadow host's children.
Upper-boundary Encapsulation
To maintain the upper-boundary encapsulation, the following scoping constraints must apply to all nodes in a shadow DOM subtree:
- The
ownerDocument
property refers to the document of the shadow host - The nodes and named elements are not accessible using shadow host's document DOM tree accessors
- The nodes with a unique id and named elements are not addressable from any attributes of elements in shadow host's document
- The stylesheets, represented by the nodes are not accessible using shadow host document's CSSOM extensions
- The nodes are accessible using shadow root's DOM tree accessor methods
- The nodes with a unique id and named elements are addressable from any attributes of elements in the same shadow DOM subtree
- The selectors must not cross the shadow boundary
For convenience, the shadow root provides its own set of DOM tree accessor methods. No nodes other than shadow root descendants are accessible with these methods.
The parentNode
and parentElement
attributes of the shadow root object must always return null
.
Lower-boundary Encapsulation
To maintain the lower-boundary encapsulation, the distribution of child nodes of the shadow host among the insertion points in the associated shadow DOM subtree must have the following traits:
- The distribution does not affect the state of the document DOM tree or shadow DOM subtrees
- Each insertion point participates in distribution by providing a matching criteria for the child nodes. The matching criteria determines whether a given node could be distributed to a given insertion point
- The distribution is a result of executing a stable algorithm
- The distribution itself does not change the variables affecting the distribution
- The distribution reoccurs whenever any variable affecting it is changed
An insertion point may be active or inactive. An active insertion point participates in the distribution process, whereas the inactive insertion does not. If not specifically set to be inactive, the insertion point must be considered active.
The distribution algorithm must produce an outcome that is equivalent of the outcome of processing these steps:- Input
- TREE, a shadow DOM subtree
- POOL, a list of DOM nodes
- Output
- The nodes in POOL are distributed among insertion points in TREE.
- Repeat for each active insertion point in TREE, in tree order:
- Let POINT be the current insertion point
- Repeat for each node in POOL:
- Let NODE be the current node
- If the NODE matches POINT's matching criteria:
- Distribute the NODE to POINT
- Remove NODE from the POOL
- Otherwise, continue to repeat
- Continue to repeat
Matching Insertion Points
The matching criteria for insertion point is defined as a set of selector fragments. Each selector fragment is indeed a fragment in the selector (shadow-host)>(fragment)
, where (shadow-host)
is a selector that uniquely identifies the shadow host, and (fragment)
is the selector fragment.
A valid selector fragment may contain:
- A type selector or a universal selector
- class selector(s)
- An ID selector
- attribute selector(s)
- the following pseudo-class selector(s):
:link
:visited
:target
:enabled
:disabled
:checked
:indeterminate
:nth-child()
:nth-last-child()
:nth-of-type()
:nth-of-last-type()
:first-child
:last-child
:first-of-type
:last-of-type
:only-of-type
If any other types of selectors are present the selector fragment, the fragment must be considered invalid.
A conforming UAs must consider a node as matching a set of selector fragments in the context of a given shadow host, if it:
- is a child node of the shadow host; and
- all selector fragments in the set are valid; and
- child node matches at least one selector fragment in the set or the set is empty.
Hosting Multiple Shadow Subtrees
A shadow host may host more than one shadow DOM subtree. In such cases, the subtrees are stacked in the order they were added the the host, starting with the subtree added most recently. This set of trees is called a tree stack. The more recently added subtree is called the younger tree, and the less recently added subtree is called the older tree. The most recently added subtree is called the youngest tree.
To facilitate composing multiple shadow subtrees of the same host, a special kind of insertion point is defined. The shadow insertion point designates a place in the shadow DOM subtree, where an older tree is inserted.
Just like other insertion points, the shadow insertion points can be active or inactive.
The composition is performed with the tree composition algorithm, which must be equivalent to processing the following steps:
- Input
- HOST, a shadow host
- Output
- All insertion points and shadow insertion points are populated.
- Let TREE be the youngest tree in the HOST's tree stack
- Let POOL be the list of nodes
- Populate POOL with child nodes of the HOST
- Repeat while TREE exists:
- Let POINT be the first encountered active shadow insertion point in TREE, in tree order
- Run the distribution algorithm, supplying POOL and TREE as input
- If POINT exists:
- Find the next older tree, relative to TREE in the HOST's tree stack
- If there is no older tree:
- Distribute remaining nodes in POOL, if any, to POINT
- stop.
- Otherwise:
- Set TREE to be this older tree
- Assign TREE to the POINT
- Continue to repeat
- If there is no older tree:
- Find the next older tree, relative to TREE in the HOST's tree stack
- Otherwise, stop.
When an insertion point or a shadow insertion point has nothing assigned or distributed to them, the fallback content must be used instead when rendering. The fallback content is all descendants of the DOM element that represents the insertion point. The insertion points or shadow insertion points in fallback content must be considered inactive.
Nested Shadow Subtrees
Any element in a shadow DOM subtree can be a shadow host, thus producing nested shadow DOM subtrees. A shadow DOM subtree is nested when its shadow host is itself a part of a shadow DOM subtree.
Rendering Shadow DOM Subtrees
Rendering of shadow DOM subtrees, or presenting them visually, is defined as a specialization of rendering any DOM subtree, and must happen as these steps:
- Input
- HOST, a shadow host
- Output
- Rendering of the HOST, including its shadow DOM subtrees
- Run tree composition algorithm for the given shadow host
- As content of the shadow host, render the youngest tree as a any DOM subtree, with the following shadow rendering exceptions:
- Each element that is a shadow host must be rendered accordingly
- In place of each insertion point, process the following insertion point rendering steps:
- If there are child nodes distributed to this insertion point, for each child node and in the order they were distributed:
- Let CHILD be the child node being rendered
- If CHILD is itself an insertion point, and the tree being rendered is nested, render them using insertion point rendering steps
- Otherwise, render CHILD as any DOM subtree
- Otherwise, render fallback content as any DOM subtree
- If there are child nodes distributed to this insertion point, for each child node and in the order they were distributed:
- In place of each shadow insertion point:
- If there is an older tree, assigned to this shadow insertion point, render it as any DOM subtree, but with the same shadow rendering exceptions.
- Otherwise, render fallback content as any DOM subtree
This process of rendering produces a structure that is a composition of several DOM subtrees, including the document tree. The term "as rendered" is used to refer to this structure.
Events
In shadow DOM subtrees, the DOM Events are allowed to cross the shadow boundaries under some conditions.
When an event is fired in a shadow DOM subtree, it either escapes out of the shadow tree during the capture, target, or bubble phases or it is stopped at the shadow boundary.
In terms of DOM event dispatch, this manifests in populating the list of ancestors of the target node with adjusted parent nodes, or nodes that appear as parents as rendered. The parent calculation algorithm is used to determine the adjusted parent of any given node and create the list of ancestors for event dispatch. This algorithm must be equivalent to processing the following steps:
- Input
- NODE, a DOM node
- Output
- PARENT, a DOM node's adjusted parent
- If NODE is currently distributed to an insertion point in a shadow DOM subtree
- Let the PARENT be the insertion point to which the NODE is distributed to
- If NODE is a shadow root:
- If NODE is currently assigned to a shadow insertion point:
- Let the PARENT be the shadow insertion point to which NODE is assigned to
- Otherwise, let the PARENT be the shadow host of the NODE
- If NODE is currently assigned to a shadow insertion point:
- Otherwise, let PARENT be the node's parent node.
Event Retargeting
In the cases where event escapes the shadow tree, the event's information about the target of the event is adjusted in order to maintain upper boundary encapsulation. Event retargeting is a process of computing relative targets for each ancestor of the node at which the event is dispatched. A relative target is a DOM node that most accurately represents the target of a dispatched event at a given ancestor while maintaining the upper boundary encapsulation.
The retargeting algorithm is used to determine relative targets, and it must be equivalent to processing the following steps:
- Input
- NODE, a DOM node
- Output
- TARGETS, a list of tuples, each containing NODE's ancestor and its relative target
- Let BOUNDARY be a flag, initialized to false
- Let TARGET be NODE
- Let ANCESTOR be NODE
- Repeat while ANCESTOR exists:
- If ANCESTOR is a shadow root, set BOUNDARY to true
- Otherwise:
- If BOUNDARY is set to true
- Set TARGET to ANCESTOR
- Set BOUNDARY to false state
- Add (TARGET, ANCESTOR) tuple to TARGETS
- If BOUNDARY is set to true
- Set ANCESTOR to be the result of parent calculation algorithm, given ANCESTOR as input
The retargeting process must occur prior to dispatch of an event.
Retargeting relatedTarget
Some events have a relatedTarget
property, which holds a node that's not the event's target, but is related to the event.
For instance, a mouseover
event's relatedTarget
may hold the node from which the mouse has moved to event's target
. In the case where relatedTarget
is in a shadow DOM subtree, the conforming UAs must not leak its actual value outside of this subtree. In cases where both relatedTarget
and target
are part of the same shadow DOM subtree, the conforming UAs must stop events at the shadow boundary to avoid the appearance of spurious mouseover
and mouseout
events firing from the same node.
Thus, if an event has a relatedTarget
, its value and extent of event dispatch must be adjusted. In general:
- If
relatedTarget
andtarget
are enclosed by a shadow DOM subtree, the event dispatch must be stopped at the shadow boundary of this subtree. This boundary is called lowest common boundary - If
relatedTarget
is separated fromtarget
by one or more shadow boundaries, it must be retargeted to the boundary that's closest totarget
. This boundary is called first divergent boundary.
The related target resolution algorithm is used to determine dispatch behavior of events with a relatedTarget
property, which must be equivalent to processing the following steps:
- Input
- TARGET, the target DOM node of the event
- RELATED, the related target DOM node of the event
- Output
- LIMIT, the ancestor DOM node of TARGET, at which the event dispatch must be stopped (not including LIMIT itself)
- ADJUSTED, the adjusted related target DOM node
- Let COMMON be undefined
- Let ANCESTOR be the lowest common ancestor of TARGET and RELATED, computed using parent calculation algorithm when determining ancestry
- If ANCESTOR exists:
- If ANCESTOR is RELATED and is a shadow host:
- Set LIMIT to ANCESTOR
- Set ADJUSTED to RELATED
- Stop.
- Otherwise:
- Let ROOT be the nearest ancestor shadow root of ANCESTOR
- If ROOT exists, set COMMON and LIMIT to ROOT's shadow host
- If ANCESTOR is RELATED and is a shadow host:
- If COMMON is still undefined, set COMMON to the farthest ancestor of RELATED, computed using parent calculation algorithm when determining ancestry
- Let HOST be the farthest shadow host ancestor of RELATED that is also a descendant of COMMON, computed using parent calculation algorithm when determining ancestry
- If HOST exists, set ADJUSTED to HOST
- Otherwise, set ADJUSTED to RELATED
Retargeting Focus Events
The focus
, DOMFocusIn
, blur
, and DOMFocusOut
events must be treated in the same way as events with a relatedTarget
, where the corresponding node that is losing focus as a result of target
gaining focus or the node that is gaining focus, and thus causing the blurring of target
acts as the related target.
Events that are Always Stopped
Mutation events and selectstart
event are always stopped at the nearest shadow boundary.
Event Dispatch
At the time of event dispatch:
- The
Event
target
andcurrentTarget
attributes must return the relative target for the node on which event listeners are invoked - The
UIEvent
relatedTarget
attribute must return the adjusted related target - When capturing, which entails processing step 3 of the event dispatch algorithm, the
Event
eventPhase attribute must return AT_TARGET if the relative target is same as the node on which event listeners are invoked - When bubbling, which entails processing step 6 of the event dispatch algorithm, the event listeners must not be invoked on a node if it is the same as its relative target
Upon completion of the event dispatch, the Event
object's target
and currentTarget
must be to the highest ancestor's relative target. 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 DOM subtrees.
Event Retargeting Example
Suppose we have a user interface for a media controller, represented by this DOM tree, composed of both document and the shadow DOM subtrees. 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-boundary
element to demarcate the shadow boundaries:
<div id="player">
<shadow-boundary>
<div class="controls">
<button class="play-button">
<input type="range" class="timeline">
<shadow-boundary>
<div class="slider-thumb"></div>
</shadow-boundary>
</input>
<div class="volume-slider-container">
<input type="range" class="volume-slider">
<shadow-boundary>
<div class="slider-thumb"></div>
</shadow-boundary>
</input>
</div>
</div>
</shadow-boundary>
</div>
Let's have a user position their pointing device over the volume slider's thumb (#player .volume-slider .slider-thumb
), thus triggering a mouseover
event on that node. For this event, let's pretend it has no associated relatedTarget
.
Just before the event is dispatched, we perform retargeting:
- We set the relative target of the thumb node to itself.
- Walking up, we find a shadow boundary and skip it.
- Next up is an input (
#player .volume-slider
), and we set its relative target to itself, since the previous ancestor was a shadow boundary. - We then walk up to the containing div (
#player .volume-slider-container
), and use previous ancestor's target -- the input element. - Walking up one more, we reach the controls panel (
#player .controls
), and again use input element as relative target per retargeting algorithm. - Next, we reach another shadow boundary and skip it.
- Finally we walk up to the player element (
#player
) and set its relative target to itself.
<div id="player"> 7
<shadow-boundary> 6
<div class="controls"> 5
<button class="play-button">
<input type="range" class="timeline">
<shadow-boundary>
<div class="slider-thumb"></div>
</shadow-boundary>
</input>
<div class="volume-slider-container"> 4
<input type="range" class="volume-slider"> 3
<shadow-boundary> 2
<div class="slider-thumb"></div> 1
</shadow-boundary>
</input>
</div>
</div>
</shadow-boundary>
</div>
At the end of this process, we should have the following set of ancestors and relative targets:
Ancestor | Relative Target |
---|---|
... |
div#player |
div#player |
div#player |
div#player .controls |
div#player .controls .volume-slider |
div#player .volume-slider-container |
div#player .controls .volume-slider |
div#player .controls .volume-slider |
div#player .controls .volume-slider |
div#player .controls .volume-slider .slider-thumb |
div#player .controls .volume-slider .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
(#player .timeline .slider-thumb
). This triggers both a mouseout
event for the volume slider thumb and the mouseover
event for the timeline thumb.
Let's study how the relatedTarget
value of the volume thumb's mouseout
event is affected and whether event escapes out of the player. For this event, the relatedTarget
is the timeline thumb (#player .timeline .slider-thumb
).
- We find lowest common ancestor of timeline thumb and volume slider thumb: the controls panel (
#player .controls
). - Walking up, we look for a shadow boundary, and find one just above. This is the lowest common boundary.
- Given that the lowest common boundary exists, we limit event dispatch to the descendants of this boundary.
- Starting from the lowest common boundary, we walk down the ancestors of the timeline thumb, looking for a shadow boundary. We find it just below the timeline (
#player .timeline
). This is the first divergent boundary. - We adjust
relatedTarget
to be the parent of the first divergent boundary, the timeline (#player .timeline
). We are now ready to dispatch this event.
<div id="player">
<shadow-boundary> — lowest common boundary
<div class="controls">
<button class="play-button">
<input type="range" class="timeline">
<shadow-boundary> — first divergent boundary
<div class="slider-thumb"></div>
</shadow-boundary>
</input>
<div class="volume-slider-container">
<input type="range" class="volume-slider">
<shadow-boundary>
<div class="slider-thumb"></div>
</shadow-boundary>
</input>
</div>
</div>
</shadow-boundary>
</div>
Styles
To enforce upper-boundary encapsulation, CSS rules declared by the document do not apply in a shadow DOM subtree, unless the apply-author-styles flag is set for this subtree. The flag signals that document CSS rules are applicable in the shadow DOM subtree.
Conversely, to enforce lower-boundary encapsulation, CSS rules from a shadow DOM subtree do not apply to the nodes, distributed to the insertion points.
The rule applicability algorithm is used to determine whether any given rule is applicable to any DOM node, and it must be equivalent to processing the following steps:
- Input
- RULE, a CSS rule whose applicability is being determined
- NODE, a DOM node
- Output
- Whether RULE must be applied to NODE.
- If RULE is not an author style, the rule is applicable to NODE.
- Otherwise:
- Let TREE be the DOM subtree to which NODE belongs. The TREE is then the document tree, a shadow DOM subtree, or some disconnected tree
- In the case TREE is a disconnected tree, the rule is not applicable to NODE.
- In the case TREE is a shadow DOM subtree:
- If TREE has the apply-author-styles flag is set for TREE, RULE is applicable to NODE.
- Otherwise, if RULE is declared with a
style
element:- Let STYLE be this style element
- If STYLE has a
scoped
attribute and STYLE is in TREE, then RULE is applicable to NODE.
- Otherwise, RULE is not applicable to NODE.
- In the case TREE is a document tree:
- If RULE is declared in TREE, the rule is applicable to NODE.
- Otherwise, RULE is not applicable to NODE.
The effect of this algorithm is that any unscoped styles, declared in a shadow DOM subtrees, are applied neither to document nor the subtree in which they are declared.
In a document that contains shadow DOM subtrees, the CSS properties must be inherited from parent nodes, produced using parent calculation algorithm. This requirement has the following effects:
- the styles of the shadow host are inherited by the children of the shadow root
- the styles of the insertion point node are inherited by those child nodes of the shadow host that are assigned to this insertion point
- the styles of the shadow insertion point node are inherited by the child nodes of the shadow root of the shadow DOM subtree, distributed to this shadow insertion point
CSS Variables
The of shadow host styles being inherited by the children of the shadow root must also apply to CSS Variables. This provides a way for document DOM tree styles to send signals into the shadow DOM subtrees, and for the shadow DOM subtrees to receive these signals with the use of the data()
function.
text-decoration
Property
The text decorations, specified by the text-decoration
property are not propagated from shadow hosts to shadow DOM subtrees.
User Interaction
Ranges and Selections
Since a DOM node in a document tree and a DOM node in a shadow DOM subtree never have the same root, there may never exist a valid DOM range that spans either both a document tree and a shadow DOM subtree, or multiple shadow DOM subtrees.
Accordingly, selections may only exist within one tree, because they are defined by a single range. To maintain upper boundary encapsulation, the selection, returned by the window.getSelection()
method must never return a selection within a shadow DOM subtree.
Focus Navigation
Shadow DOM subtrees participate in sequential or directional focus navigation as part of the document tree. The navigation order within the shadow DOM subtrees must be determined using the as-rendered structure and is called shadow DOM navigation order.
For sequential focus navigation, the shadow DOM navigation order sequence must be inserted into the document navigation order:
- immediately after the shadow host, if the shadow host is focusable; or
- in place of the shadow host as if the shadow host were assigned the value of
auto
for determining its position.
For directional focus navigation, it is up to the user agent to integrate the shadow DOM navigation order into the document navigation order.
Editing
The value of the contenteditable
attribute must not propagate from shadow host to its shadow DOM subtrees.
Assistive Technology
User agents with assistive technology traverse the document tree as rendered, and thus enable full use of of WAI-ARIA semantics in the shadow DOM subtrees.
HTML Elements in Shadow DOM Subtrees
Comparatively, a shadow DOM subtree can be seen as somewhere between just a DOM subtree in a document and a document fragment. Since it is rendered, a shadow DOM subtree aims to retain the traits of a typical DOM subtree in a document. At the same time, it is an encapsulation abstraction, so it has to avoid affecting the document DOM tree. Thus, the HTML elements behave as specified in the shadow DOM subtrees, with a few exceptions.
Inert HTML Elements
A subset of HTML elements behaves as inert, or not part of the document tree. This is consistent how the HTML elements would behave in a document fragment. These elements are:
All other HTML elements in the shadow DOM subtrees behave as if they were part of the document tree, though scoped to their subtrees.
HTML Forms
The forms in HTML are scoped at the document level. That is, all form
elements and form-associated elements are accessible using the document DOM object's tree accessors. Per scoping constraints, this excludes elements in the shadow DOM subtrees.
Instead, each shadow DOM subtree scopes its form
elements and form-associated elements. Because the form
's ownerDocument
is the shadow host's document, the form submission works as specified.
Elements and DOM Objects
ShadowRoot
Object
The ShadowRoot
object represents the shadow root.
[Constructor(in HTMLElement host) raises (DOMException)]
interface ShadowRoot : DocumentFragment {
HTMLElement getElementById(in DOMString elementId);
NodeList getElementsByClassName(in DOMString tagName);
NodeList getElementsByTagName(in DOMString className);
NodeList getElementsByTagNameNS(DOMString namespace, DOMString localName);
attribute bool applyAuthorStyles;
readonly attribute Element host;
attribute DOMString innerHTML;
}
ShadowRoot implements NodeSelector;
ShadowRoot
Attributes
- Represents the apply-author-styles flag and indicates whether or not the rules in author styles associated with the element's document apply to the shadow DOM subtree. If
false
(default value), the author styles are not applied to the shadow DOM subtree. Iftrue
, the author styles are applied. host
of typeElement
, readonly- refers back to the shadow host. This value is never
null
. innerHTML
of typeDOMString
- represents the markup of
ShadowRoot
's contents, and operates as specified, givenShadowRoot
as the context object.
The nodeType
attribute of a ShadowRoot
instance must return DOCUMENT_FRAGMENT_NODE
. Accordingly, the nodeName
attribute of a ShadowRoot
instance must return "#document-fragment"
.
ShadowRoot
Methods
constructor
-
Creates new shadow root and associates it with the shadow host.
Parameter Type Nullable Optional Description element
Element
No No Element that will be hosting this instance of the shadow DOM subtree. Exceptions:HIERARCHY_REQUEST_ERR
- Thrown when the
element
parameter is not a validElement
.
Returns: a newShadowRoot
instance. getElementById
- Behaves exactly like document.getElementById, except scoped to the shadow DOM subtree.
getElementsByClassName
- Behaves exactly like document.getElementsByClassName, except scoped to the shadow DOM subtree.
getElementsByTagName
- Behaves exactly like document.getElementsByTagName, except scoped to the shadow DOM subtree.
getElementsByTagNameNS
- Behaves exactly like document.getElementsByTagNameNS, except scoped to the shadow DOM subtree.
Invoking the cloneNode()
method on a ShadowRoot
instance must always throw a DATA_CLONE_ERR
exception.
The content
element
The content
element represents an insertion point in the shadow DOM subtree.
- Context
- Where flow content is expected.
- Content model
- Transparent
- Children
- Anything as fallback content
- Content attributes
- Global attributes
-
select
, a set of comma-separated tokens-
defines the matching criteria for distributing child nodes of the host element. Each token must be a valid selector fragment. If any token is invalid, the entire value of the
select
attribute is considered invalid.
- DOM Interface
-
interface HTMLContentElement : HTMLElement { attribute DOMString select; }
- Attributes
select
of typeDOMString
-
- Must reflect the select attribute.
The shadow
element
The shadow
element represents an shadow insertion point in a shadow DOM subtree.
- Context
- Where flow content is expected.
- Content model
- Transparent
- Children
- Anything as fallback content
- Content attributes
- Global attributes
- DOM Interface
-
interface HTMLShadowElement : HTMLElement { }
Shadow DOM Example
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"><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/4">Awesome story</a></li>
<li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
</ul>
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 DOM subtree, to be hosted by the ul
element:
<div class="breaking">
<ul>
<content select=".breaking"></content> <!-- insertion point for breaking news -->
</ul>
</div>
<div class="other">
<ul>
<content></content> <!-- insertion point 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 DOM subtree mockup:
<style scoped>
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, contentSelector)
{
var group = document.createElement('div');
group.className = className;
// Empty string in select attribute or absence thereof work the same, so no need for special handling.
group.innerHTML = '<ul><content select="' + contentSelector + '"></content></ul>';
return group;
}
function createStyle()
{
var style = document.createElement('style');
style.scoped = true;
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 makeShadowSubtree(storyList)
{
var root = new ShadowRoot(storyList);
root.appendChild(createStyle());
root.appendChild(createStoryGroup('breaking', '.breaking'));
root.appendChild(createStoryGroup('other', ''));
}
document.addEventListener('DOMContentLoaded', function {
[].forEach.call(document.querySelectorAll('ul.stories'), makeShadowSubtree);
});
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 WoW.
A few months pass.
It's election time. With Bob at his annual conference, Alice is charged with adding another, temporary box to the news widget, filled with election-related stories. Alice studies Bob's code, reads up on the shadow DOM spec and realizes that, thanks to multiple shadow DOM subtree support, she doesn't have to touch his code. As usual, her solution is elegant and simple, fitting neatly right under Bob's code:
// TODO(alice): BEGIN -- DELETE THIS CODE AFTER ELECTIONS ARE OVER.
var ELECTION_BOX_REMOVAL_DEADLINE = ...;
function createElectionStyle()
{
var style = document.createElement('style');
style.scoped = true;
// TODO(alice): Check designer's desk for hallucinogens.
style.textContent = 'div.election { color: Magenta;font-size: 24px; border: 2px dotted Fuchsia; }';
return style;
}
function makeElectionShadowSubtree(storyList)
{
var root = new ShadowRoot(storyList);
// Add and style election story box.
root.appendChild(createElectionStyle());
root.appendChild(createStoryGroup('election', '.election'));
// Insert Bob's shadow tree under the election story box.
root.appendChild(document.createElement('shadow'));
}
if (Date.now() < ELECTION_BOX_REMOVAL_DEADLINE) {
document.addEventListener('DOMContentLoaded', function {
[].forEach.call(document.querySelectorAll('ul.stories'), makeElectionShadowSubtree);
});
}
// TODO(alice): END -- DELETE THIS CODE AFTER ELECTIONS ARE OVER.
Using the shadow
element allows Alice to compose Bob's widget inside of hers—without having to change a line of production code. Smiling to herself, Alice realizes that Bob may have come up with a way to keep the document markup clean, but she is the one who takes the cake for using shadow DOM subtree composition in such a cool way.
Acknowledgements
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, Brian Kardell, Darin Fisher, Edward O'Connor, Elisée Maurer, Erik Arvidsson, Hayato Ito, Jonas Sicking, Malte Ubl, Olli Pettay, Rafael Weinstein, Sam Dutton, and Tab Atkins 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.