Copyright © 2013 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved. W3C liability, trademark and document use rules apply.
This document is a non-normative reference, which provides an overview of Web Components. It summarizes the normative information in the respective specifications in easy-to-digest prose with illustrations.
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.
This document was published by the Web Applications Working Group as a First Public Working Draft. If you wish to make comments regarding this document, please send them to public-webapps@w3.org (subscribe, archives). All feedback is welcome.
Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. This document is informative only. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
As mentioned earlier, this document is a work in progress. It relates all concepts associated with Web Components into one coherent, non-normative narrative.
Each concept is separately being refined and developed in a normative document.
This is an iterative process.
What is described here may sometimes lag behind the normative documents, and may sometimes be pointing the way ahead for those documents. Once all of the forward-looking information is extracted into the respective normative documents, this document will undergo an ultimate update to accurately summarize the information in the normative documents.
The component model for the Web ("Web Components") consists of five pieces:
Each of these pieces is useful individually. When used in combination, Web Components enable Web application authors to define widgets with a level of visual richness and interactivity not possible with CSS alone, and ease of composition and reuse not possible with script libraries today.
This document discusses each of these pieces. Each section builds on the last to illustrate how authors can use the pieces of Web Components in combination.
The HTML Templates specification is the normative description of this part of Web Components.
The <template>
element contains markup intended to be used later. The content of the <template>
element is parsed by the parser, but it is inert: scripts aren't processed, images aren't downloaded, and so on. The <template>
element is not rendered.
The <template>
element has property called content
which holds the content of the template in a document fragment. When the author wants to use the the content they can move or copy the nodes from this property:
<template id="commentTemplate">
<div>
<img src="">
<div class="comment-text"></div>
</div>
</template>
<script>
function addComment(imageUrl, text) {
var t = document.querySelector("#commentTemplate");
var comment = t.content.cloneNode(true);
// Populate content.
comment.querySelector('img').src = imageUrl;
comment.querySelector('.comment-text').textContent = text;
document.body.appendChild(comment);
}
</script>
Because what is contained in the template is not in the document, but in the content
document fragment, appending a copy of the template content in this way makes it "live" so scripts run and images are fetched at this point. For example, templates can be used to store large scripts inline in the document, but not incur the cost of parsing them until they are needed.
Decorators, unlike other parts of Web Components, do not have a specification yet. One concept for Decorators that could be pursued is sketched in this section. There is no normative description of this part of Web Components.
A decorator is something that enhances or overrides the presentation of an existing element. Like all presentation aspects, the application of decorators is controlled by CSS. However, being able to specify the extra presentation using markup is unique to decorators.
The <decorator>
element contains a <template>
element, which specifies the markup to use for rendering the decoration.
<decorator id="details-open">
<template>
<a id="summary">
▾
<content select="summary"></content>
</a>
<content></content>
</template>
</decorator>
The <content>
element is the point where the content of the decorated element (the element's children) should appear in the rendering.
Decorators are applied using the decorator
CSS property:
details[open] {
decorator: url(#details-open);
}
This decorator and stylesheet would cause the following markup:
<details open>
<summary>Timepieces</summary>
<ul>
<li>Sundial
<li>Cuckoo clock
<li>Wristwatch
</ul>
</details>
To be rendered like this:
▾ TimepiecesEven though the decorator
CSS property can point to any resource on the Web, the decorator will not be applied unless its definition is loaded by this document. The markup that generates the presentation is limited to be purely presentational: it may never run script (including inline event handlers) and it cannot be editable.
Decorators can also attach event handlers to implement interactivity.
Because the decorators are transient, it is not useful for the decorator to attach event listeners to the nodes in the template or rely on any state, since it perishes any time the decorator is applied or unapplied. Instead, decorator events are mediated by an event controller.
To register an event listener with an event controller, the template includes a <script>
element. The script is run once when the decorator
element is inserted into the document, or loaded as part of an external document. The script must evaluate to an array of registrations:
<decorator id="details-open">
<script>
function clicked(event) {
event.target.removeAttribute('open');
}
[{selector: '#summary', type: 'click', handler: clicked}];
</script>
<template>
<a id="summary">
<!-- as illustrated above -->
The event controller interprets the array and routes events originating in any of the places that the decorator is applied to the event handler.
When the event listener is called, the target of the event is the content that the decorator was applied to, not content from the template. In the above example, clicks on the ▾ (which is defined in the template) are delivered to the clicked
function (which is registered to the #summary
element, also defined in the template) but the event.target
property refers to the decorated <details>
element. This retargeting is necessary because the decorator specifies presentation; it does not affect the DOM structure of the document.
By removing the open
attribute, this decorator will no longer apply because the selector with the decorator
property no longer matches. The decorator is unapplied, returning the rendering of the element back to the pre-decoration state. However the author could write a second decorator to affect a "closed" rendering and in this way implement stateless interactivity simply by triggering different decorators:
<style>
details {
decorator: url(#details-closed);
}
details[open] {
decorator: url(#details-open);
}
</style>
<decorator id="details-closed">
<script>
function clicked(event) {
event.target.setAttribute('open', 'open');
}
[{selector: '#summary', type: 'click', handler: clicked}];
</script>
<template>
<a id="summary">
▸ <content select="summary"></content>
</a>
</template>
</decorator>
<decorator id="details-open">
<!-- as illustrated above -->
This uses two decorators. One presents the details view closed; the other presents the details view open. Each decorator uses an event handler to respond to clicks by toggling the open state of the element. The content element's select attribute will be explained in detail later.
The Custom Elements specification is the normative description of this part of Web Components.
Custom elements are new types of DOM elements that can be defined by authors. Unlike decorators, which are stateless and ephemeral, custom elements can encapsulate state and provide script interfaces. The following table summarizes the key differences between decorators and custom elements.
Decorators | Custom Elements | |
---|---|---|
Lifetime | Ephemeral, while a CSS selector matches | Stable, matches entire element lifetime |
Applied, unapplied dynamically | Yes, based on CSS selectors | No, fixed at element creation time |
Accessible through script | No, transparent to DOM; cannot add interfaces | Yes, accessible through DOM; can provide interfaces |
State | Stateless projection | Stateful DOM object |
Behavior | Simulated by changing decorators | First-class using script and events |
The <element>
element defines a custom element. It specifies the type of element it's a refinement of using the extends
attribute:
<element extends="button" name="fancy-button">
…
</element>
The extends
attribute specifies the tag name of the kind of element this element is an extension of. Instances of the custom element will have the tag name specified here.
The name
attribute specifies the name of the custom element, by which it will be referred to in markup. These names must include a hyphen.
Because not all user agents support custom elements, authors should extend the HTML element that has the closest meaning to their new kind of element. For example, if they are defining a custom element that is interactive and responds to clicks by doing some action, they should extend button.
When there isn't a HTML element that is semantically close to their custom element, authors can omit the extends
attribute. These elements will use the value of the name
attribute as a tag name. For this reason, these kinds of elements are called custom tags. A user agent that does not support custom elements will treat this as the semantically undifferentiated HTMLUnknownElement.
You can define a custom element's script API by putting methods and properties on the custom element's prototype object using a nested <script>
element:
<element name="tick-tock-clock">
<script>
({
tick: function () {
…
}
});
</script>
</element>
The properties of the last value of the script will be copied to a prototype object created for you. In the above example, <tick-tock-clock>
elements will have a tick
method.
Custom elements have lifecycle callbacks which can be used to set up presentational aspects of a custom element. These are: readyCallback
, which is called after a custom element is created; insertedCallback
, which is called after a custom element is inserted into a document; and removedCallback
, which is called after a custom element is removed from a document.
The following example demonstrates using a template, shadow DOM (described in detail below), and lifecycle callbacks to create a working clock element:
<element name="tick-tock-clock">
<template>
<span id="hh"></span>
<span id="sep">:</span>
<span id="mm"></span>
</template>
<script>
var template = document.currentScript.parentNode.querySelector('template');
function start() {
this.tick();
this._interval = window.setInterval(this.tick.bind(this), 1000);
}
function stop() {
window.clearInterval(this._interval);
}
function fmt(n) {
return (n < 10 ? '0' : '') + n;
}
({
readyCallback: function () {
this._root = this.createShadowRoot();
this._root.appendChild(template.content.cloneNode());
if (this.parentElement) {
start.call(this);
}
},
insertedCallback: start,
removedCallback: stop,
tick: function () {
var now = new Date();
this._root.querySelector('hh').textContent = fmt(now.getHours());
this._root.querySelector('sep').style.visibility =
now.getSeconds() % 2 ? 'visible' : 'hidden';
this._root.querySelector('mm').textContent = fmt(now.getMinutes());
},
chime: function () { … }
});
</script>
</element>
Because custom elements use existing HTML tag names—div
, button
, option
, and so on—we need to use an attribute to specify when an author intends to use a custom element. The attribute name is is
, and its value is the name of a custom element. For example:
<element extends="button" name="fancy-button"> <!-- definition -->
…
</element>
<button is="fancy-button"> <!-- use -->
Do something fancy
</button>
As an alternative to the <element>
element, you can also register custom elements from script using the register
method. In this case you can set up the element's methods and properties by manipulating the prototype object directly. register
returns a function that can be called to create an instance of the custom element:
var p = Object.create(HTMLButtonElement.prototype, {});
p.dazzle = function () { … };
var FancyButton = document.register('button', 'fancy-button', {prototype: p});
var b = new FancyButton();
document.body.appendChild(b);
b.addEventListener('click', function (event) {
event.target.dazzle();
});
Custom elements defined either using <element>
or register
can be instantiated from script using the standard createElement
method:
var b = document.createElement('button', 'fancy-button');
alert(b.outerHTML); // will display '<button is="fancy-button"></button>'
var c = document.createElement('tick-tock-clock');
alert(c.outerHTML); // will display '<tick-tock-clock></tick-tock-clock>'
As the definition of a custom element is loaded, each element that matches the definition is upgraded. The upgrade process updates the prototype of the element, which makes the element's API available to script, and invokes lifecycle callbacks.
You can avoid the flash of unstyled content by using the CSS :unresolved
pseudoclass. This pseudoclass will match any custom element that does not have a definition available yet:
<style>
tick-tock-clock:unresolved {
content: '??:??';
}
</style>
<tick-tock-clock></tick-tock-clock> <!-- will show ??:?? -->
This pseudoclass can also be used by script to avoid interacting with elements that have not been upgraded yet:
// Chime ALL the clocks!
Array.prototype.forEach.call(
document.querySelectorAll('tick-tock-clock:not(:unresolved)'),
function (clock) { clock.chime(); });
An element that wants to notify other parts of the page that it has been upgraded could dispatch a custom event to do so. Scripts that want to delay interacting with the element until it has been upgraded can listen to this event.
In addition to HTML elements, you can also extend a custom element by specifying the custom element's name as the value of the extends
attribute in the <element>
element, or by setting up a prototype chain that includes another custom element prototype:
<element extends="tick-tock-clock" name="grand-father-clock">
…
</element>
<script>
var p = Object.create(Object.getPrototypeOf(document.createElement('tick-tock-clock')));
p.popOutBirdie = function () { … }
var CuckooClock = document.register('cuckoo-clock', {prototype: p});
var c = new CuckooClock();
c.tick(); // inherited from tick-tock-clock
c.popOutBirdie(); // specific to cuckoo-clock
</script>
The Shadow DOM specification is the normative description of this part of Web Components.
Shadow DOM is an adjunct tree of DOM nodes. These shadow DOM subtrees can be associated with an element, but do not appear as child nodes of the element. Instead the subtrees form their own scope. For example, a shadow DOM subtree can contain IDs and styles that overlap with IDs and styles in the document, but because the shadow DOM subtree (unlike the child node list) is separate from the document, the IDs and styles in the shadow DOM subtree do not clash with those in the document.
Shadow DOM can be applied to an element by calling the createShadowRoot
method. This returns a ShadowRoot node which can then be populated with DOM nodes.
An element with shadow DOM is called a shadow host. When an element has shadow DOM, the element's children are not rendered; the content of the shadow DOM is rendered instead.
A shadow DOM subtree can use a <content>
element to specify an insertion point in the rendered output. The host's children are displayed at the insertion point. The <content>
element acts as an insertion point for rendering only—it does not change where the elements appear in DOM.
You can have more than one insertion point in the shadow DOM subtree! The select
attribute lets you choose which children appear at which insertion point, as illustrated in the details-open
decorator example:
<a id="#summary">
▾
<content select="summary"></content>
</a>
<content></content>
Insertion points let you reordered or selectively omit rendering the host's children, but they will not cause something to be rendered multiple times. Tree order dictates when each of these elements takes a pass at selecting the children. Once the child is selected to be rendered at one insertion point, it can't be claimed by another one, which is why the details-open
decorator only renders the summary once.
Elements in a shadow DOM subtree may have shadow roots of their own. When this happens, the insertion points of the nested shadow DOM subtree act on the nodes in the outer shadow DOM subtree's insertion points, as this example illustrates:
<!-- document -->
<div id="news">
<h1>Good day for kittens</h1>
<div class="breaking">Kitten rescued from tree</div>
<div>Area kitten "adorable"—owner</div>
<div class="breaking">Jiggled piece of yarn derails kitten kongress</div>
</div>
<!-- #news' shadow -->
<template id="t">
<content select="h1"></content>
<div id="ticker">
<content id="stories"></content>
</div>
</template>
<!-- #ticker's shadow -->
<template id="u">
<content class="highlight" select=".breaking"></content>
<content></content>
</template>
<script>
// Set up shadow DOM
var news = document.querySelector('#news');
var r = news.createShadowRoot();
var t = document.querySelector('#t');
r.appendChild(t.content.cloneNode(true));
var ticker = r.querySelector('#ticker');
var s = ticker.createShadowRoot();
var u = document.querySelector('#u');
s.appendChild(u.content.cloneNode(true));
</script>
Conceptually, first the document and the first shadow root is combined to create the following virtual DOM tree. The insertion points have been erased and the host element's child nodes appear in their place.
<div id="news">
<h1>Good day for kittens</h1>
<div id="ticker">
<div class="breaking">Kitten rescued from tree</div>
<div>Area kitten "adorable"—owner</div>
<div class="breaking">Jiggled piece of yarn derails kitten kongress</div>
</div>
</div>
Next, this intermediate virtual tree is combined with the second shadow root to create another virtual DOM tree. All of the breaking news is now at the top. This is because of the <content class="highlight" select=".breaking">
insertion point. Note that the elements are reordered despite the fact that the breaking
class does not appear in the #ticker
element's children, because insertion points pick from the intermediate virtual tree. Projecting a node through multiple insertion points in this way is called reprojection.
<div id="news">
<h1>Good day for kittens</h1>
<div id="ticker">
<div class="breaking">Kitten rescued from tree</div>
<div class="breaking">Jiggled piece of yarn derails kitten kongress</div>
<div>Area kitten "adorable"—owner</div>
</div>
</div>
An insertion point may have content. This is called fallback content because it is only displayed if nothing is distributed to an insertion point. For example, the news ticker shadow DOM could be modified to display default text if no heading and/or stories are available:
<!-- #news' shadow -->
<template id="t">
<content select="h1">Today's top headlines</content>
<div id="ticker">
<content id="stories">
No news
<button onclick="window.location.reload(true);">Reload</button>
</content>
</div>
</template>
Any element can have more than one shadow DOM subtree. Don't look so puzzled! In fact, this is common when you are extending a custom element that already has a shadow DOM subtree. What happens to that poor old tree? We could just ditch it for the new arboreal hotness, but what if you don't want to? What if you want reuse it?
There is another kind of insertion point for this purpose: the <shadow>
element, which pulls in the previously applied shadow DOM subtree (also known as the older tree). For example, here is a custom element which extends the <tick-tock-clock>
element and adds a direction indicator to it:
<element name="sailing-watch" extends="tick-tock-clock">
<template>
<shadow></shadow>
<div id="compass">N</div>
</template>
<script>
…
</script>
</element>
Since an element can have multiple shadows, we need to understand how these shadows interact with each other and what effect these interactions have on rendering of the element's children.
First, the order in which the shadow DOM subtrees are applied is important. Because you cannot remove a shadow root, the order is:
Next, we take this stack of shadow DOM subtrees and traverse it backwards, starting with the last-applied (or youngest) subtree. Each <content>
insertion point, encountered in tree order, grabs the host element's children that it needs as usual.
This is where things get interesting. Once we're done shuffling the children into their right places to render, we check and see if we have a <shadow>
element. If we don't, we're done.
If we do, we plop the next shadow subtree in our list in place of the <shadow>
element, and rinse-repeat first replacing <content>
insertion points, then the first <shadow>
, until we reach the end of the stack.
And then—ta-da!—we have our wondrous shadow DOM Yggdrasil, ready for rendering.
Here's an easy way to remember how this works:
<content>
insertion points.<shadow>
element, or we've processed the oldest DOM subtree for this element.When building a custom element it is natural to think about its markup (attributes and content) and script interface. It is often just as important to think about how it interacts with the styles in the page. Shadow DOM gives authors a lot of control over how content in shadow DOM interacts with styles.
The shadow DOM subtree is surrounded by an invisible boundary, which by default applies user agent styles but not author styles. However inheritance still works as usual. This is usually what you want: In the <sailing-watch>
example above, if the page is styled so that the surrounding text is a beautiful sea green, in keeping with a nautical theme, the text inside the shadow DOM (for example the "N" of the direction indicator) will be sea green too because color
is an inherited property. But if the author has styled all <div>
elements to have orange borders, the direction indicator will not have an orange border because the border properties are not inherited properties.
A shadow root has two properties to control this behavior. The first, applyAuthorStyles
, will apply the author stylesheet. Setting this property is appropriate when writing shadow DOM that should match the appearance of the surrounding content as closely as possible. There is one caveat: CSS selectors are matched against the shadow DOM subtree, not the flattened tree. Pay attention to child, descendant, sibling and "nth-of-x" selectors when using this property.
The second shadow root property which controls the effect of surrounding styles on content in a shadow DOM subtree is resetStyleInheritance
. If this property is set to true
all properties are reset to initial values at the shadow boundary.
If applyAuthorStyles
is false
and set resetStyleInheritance
is true
, you're back to a clean slate. Your element is insulated from the styles in the page—even inherited properties—and you can use a browser reset stylesheet to build up the exact style you want.
There is a similar boundary at an insertion point. The styles from the shadow DOM subtree do not apply to the host element's children distributed into an insertion point. However some insertion points, particularly ones with very specific selectors and purpose, need to style the distributed content. Shadow DOM specifies a ::distributed
pseudo-selector for this purpose. To elaborate the news example:
<!-- #ticker's shadow -->
<template id="u">
<style>
content::distributed(*) {
display: inline-block;
}
*::distributed(.breaking) {
text-shadow: 0 0 0.2em maroon;
color: orange;
}
</style>
<content class="highlight" select=".breaking"></content>
<content></content>
</template>
This contains two rules which use ::distributed
. The left half of the selector applies to the shadow DOM subtree; the part of the selector in parentheses applies to elements distributed to those insertion points. So the first content::distributed(*)
selector applies to all elements distributed to <content>
insertion points; while the second *::distributed(.breaking)
selector applies to all elements with class breaking
distributed to insertion points, giving them a healthy maroon glow. Given this specific shadow DOM the author could reduce redundancy by writing this selector .highlight::distributed(*)
instead.
Shadow DOM can also style its host element. It is natural for a custom element's shadow DOM to style the host because it is the outermost part of a widget. For example, to make the ticker to be a scrolling display instead of a static one, we can style it in an @host
@-rule:
<!-- #ticker's shadow -->
<template id="u">
<style>
@host {
:scope {
white-space: nowrap;
overflow-style: marquee-line;
overflow-x: marquee;
}
}
content::distributed(*) {
display: inline-block;
}
*::distributed(.breaking) {
text-shadow: 0 0 0.2em maroon;
color: orange;
}
</style>
<content class="highlight" select=".breaking"></content>
<content></content>
</template>
Lastly, there are two ways to permit the page to style content in a shadow DOM subtree in a controlled way. The first exposes a specific element in the shadow DOM subtree by assigning it a pseudo ID. Author styles can then refer to it as a pseudo-element. For example, the outer "news" shadow DOM may want to let the author style the ticker part of the news display. It does this by setting the pseudo
property. Author styles can then address that part of the widget:
<script>
// Set up shadow DOM
…
var ticker = r.querySelector('#ticker');
ticker.pseudo = 'x-ticker';
…
</script>
<!-- change the appearance of the ticker part -->
<style>
#news::x-ticker {
background: gray;
color: lightblue;
}
</style>
The other way to let the page selectively style content in the shadow DOM subtree is to use CSS Variables. For example, instead of hardcoding orange with maroon tint, we could encode a pair of highlight colors:
<!-- #ticker's shadow -->
<template id="u">
<style>
@host {
:scope {
white-space: nowrap;
overflow-style: marquee-line;
overflow-x: marquee;
}
}
content::distributed(*) {
display: inline-block;
}
*::distributed(.breaking) {
text-shadow: 0 0 0.2em var(highlight-accent, maroon);
color: var(highlight-primary, orange);
}
</style>
<content class="highlight" select=".breaking"></content>
<content></content>
</template>
<!-- change the appearance of the ticker part -->
<style>
#news::x-ticker {
background: gray;
color: lightblue;
var-highlight-primary: green;
var-highlight-accent: yellow;
}
</style>
A pseudo-element is useful for opening up all of the properties of a specific element for styling by the author. CSS Variables are useful for letting the author style a narrow set of properties, but ones that are specified repetitively such as the colors in a theme.
To ensure that the elements of the shadow DOM subtree are not exposed outside of the subtree, there's a fair bit of work that happens while dispatching an event from inside of the subtree.
First, some events (like mutation and selectstart events) are just plain prevented from escaping out of the shadow DOM subtree—they are never heard from the outside.
Those events that do cross the shadow DOM boundary are retargeted—their target
and relatedTarget
values are modified to point to the element that hosts the shadow DOM subtree.
In some cases, like DOMFocusIn
, mouseover
, mouseout
events have to be given extra attention: if you're moving a mouse between two elements inside of the shadow subtree, you don't want to be spamming the document with these events, since they will appear as non-sensical babbling after the retargeting. (What? the element just reported that the mouse just moved from itself back to itself?!)
The HTML Imports specification is the normative description of this part of Web Components.
Custom elements and decorators can be loaded from external files using the link tag:
<link rel="import" href="goodies.html">
Only <decorator>
elements and <element>
elements are interpreted by the user agent, although the DOM of this document is available to script through the import
property. Documents which are retrieved cross-origin use CORs to determine that the definitions are designed to run cross-site.
This section provides an index of interfaces and elements defined by Web Components, with links to their normative definitions.
Thanks to Alex Komoroske, Alex Russell, Darin Fisher, Dirk Pranke, Divya Manian, Erik Arvidsson, Hayato Ito, Hajime Morita, Ian Hickson, Jonas Sicking, Rafael Weinstein, Roland Steiner, and Tab Atkins for their comments and contributions to this document.