Who am I?

  • Self-taught developer
  • Past: spent 5 years as a PM at Mozilla
  • Now: PM at Microsoft working on developer experience
  • Technical Working Group contributor to the W3 Web Components family of specifications

What's on tap for today?

  • The Long Road to Web Components
  • Meet the Web Components Family
  • Brass Tacks on Polyfills and Use in Prod
  • Libraries, Frameworks, and Dev Resources
  • The Code-Off (OMG, what could it be?!?)
  • AMA and Open Discussion
  • 1:1 Development Help 

The Long Road to Web Components

Let's party like it's 1999!

  • Smartphones didn't exist
  • The Web just got AJAX
  • A tech bubble was brewing
  • Pluto was still a planet
  • IE 5.5 was released

The Long Road to Web Components

HTC Files & CSS Behaviors

HTC stands for "HTML Components", a non-standard component definition API built on proprietary extensions of HTML and JScript/VBScript that was introduced in IE5.5 to allow developers to specify components with encapsulated behaviors and styles.

<PUBLIC:COMPONENT tagName="checkers">
	<PUBLIC:PROPERTY NAME="boardWidth" />
	<PUBLIC:METHOD name="newGame()" />
	<PUBLIC:ATTACH event="onmouseover" onevent="mouseover()" />

<SCRIPT Language="Javascript">
    function newGame(){
      // insert code to initialize a new game here
    function mouseover(){
      // insert code to handle mouseover events
 <?IMPORT namespace="games" implementation="checkers.htc" >
 <games:checkers />

Define Components:

Import Components:

Use Component:

The Long Road to Web Components

Issues with HTCs & CSS Behaviors

  • Lacked multi-vendor buy-in
  • Mutable bindings via CSS were a mistake
  • Suffered from performance issues under normal use-cases
  • Created an entirely new DSL and a huge API surface area

Result: Never standardized, removed in IE 10

The Long Road to Web Components

A New Hope for Web Components

All was not lost! A new champion for Web Components was ready to throw down the gauntlet, enter: Dimitri Glazkov


In 2010, Glazkov started work on a new set of specs that would bring web devs the component APIs they long deserved.

The Long Road to Web Components

Meet the Web Components Family:

Custom Elements

HTML Imports

HTML Templates

Shadow DOM

Hooks for reactive composability live demo

document.registerElement('x-foo', {
    prototype: Object.create(HTMLElement.prototype, {
        bar: {
            get: function(){ return 'test' }
document.registerElement('x-super-input', {
    extends: 'input',
    prototype: Object.create(
        HTMLInputElement.prototype, {
            // custom prototype properties
document.createElement('input', 'x-super-input');
document.registerElement('x-foo', {
  prototype: Object.create(HTMLElement.prototype, {
    createdCallback: { value: function(){} },
    attachedCallback: { value: function(){} },
    detachedCallback: { value: function(){} },
    attributeChangedCallback: { value: function(){} }

Create your own elements with custom prototypes live demo

Extend existing, native elements live demo

Automatically bootstraps custom elements. No more $('.my-widget').myWidget() boilerplate and brittle DOM structures.

<x-foo>I'm an x-foo element!</x-foo>
<input is="x-super-input" />

Meet the Web Components Family

var myDiv = document.createElement('div');
var root = myDiv.createShadowRoot();
var span = myDiv.querySelector('span');
// this query will return NULL
div::shadow span { color: red; }
div /deep/ span { color: red; }
/* ::shadow selects 1st-level shadow content, while
/deep/ selects all spans at any shadow tree depth */

:host(:active) { color: red; }
/* the host node will have red text when :active */

:host-context(section) { color: red; }
/* host nodes in sections will have red text */

::content span { color: red; }
/* all spans within content insertion points  */

Create encapsulated Shadow Roots live demo

Encapsulation semi-permeably blocks content access and style leakage

Encapsulation of shadowed content access and style targeting can be breached if a developer explicitly chooses to do so​

div span { color: red; }
/* this style will not be applied */
var span = myDiv.querySelector('::shadow span');
// this query will return the shadowed span
root.innerHTML = '<content select="span"></content>' +
// only spans added to the host will be caught & shown

root.innerHTML = '<content></content><span>Foo</span>'
// all nodes added to the host will be caught & shown

Content elements dictate where injected elements are placed, and whether they are visible to the user

Meet the Web Components Family

<template id="titled_list">
        <content select="span"></content>
        <content select="li"></content>
var template = document.querySelector('#titled_list');
var clone = template.content.cloneNode(true);
var div = document.createElement('div');
var root = div.createShadowRoot();
var clone = template.content.cloneNode(true);
var title = document.createElement('span');

title.textContent = 'My List';

// the title span is captured by the content 
// node and displayed inside the H2 element

Create your own, reusable templates using HTML markup

Generate copies of your templates

Inject your template copies into a Shadow Root to use their contents just as you would any other shadow content

Meet the Web Components Family

  <link rel="import" href="/path/to/import.html">
<script async>
  function importLoad(event) { }
  function importError(event) { }

<link rel="import"
      onerror="importError(event)" />
<script async>
function importLoad(event) {
  var doc = event.target.import;
  var template = doc.querySelector('#widget_template');
  // do something with your templates, assets, etc.

Import HTML sub-documents - fetches included scripts, styles, and templates

Act on import documents when they arrive, or fail due to an error

Utilize the imported resources when they arrive by accessing the import document attached to the link element

<!-- assume this is inside the imported sub document -->
  var importDoc = document.currentScript.ownerDocument;
  // this provides a hook to the imported sub-document

  var mainDoc = document;
  // the 'document' variable is a reference to the
  // parent document that is bringing in the import

Access the importing parent document or the imported sub-document via script from within the import sub-document

Meet the Web Components Family

Brass Tacks on Polyfills & Use in Prod

Native browser support is landing, but we'll still need polyfills for the foreseeable future

The Polyfills

Native Status

  • Custom Elements & Templates: get you some!
  • HTML Imports: strict CSP environments caused issues in the past
  • Shadow DOM: not recommended for production use due to performance and API coverage issues
  • Desktop Browsers: support extends to recent versions of major browsers (Firefox, IE10+, Safari 6)
  • Mobile browsers: Android support is limited to Android 4+

Libraries, Frameworks, and Developer Resources



A lightweight library focused on Custom Elements that wraps the imperative JavaScript APIs for Custom Elements to enable lightning-fast development of components for use in production sites today.


Key Features:

  • Does not rely on Shadow DOM
  • Advanced Event and Function Pseudo systems
  • Ergonomics and performance-focused APIs and features
  • Optional hooks for easy use of advanced APIs like Shadow DOM



A component and data-binding framework built atop Web Components APIs that offers a full suite of features. Comparable in size, depth, and breadth to projects like Angular and Ember. 


Key Features:

  • Offers a declarative element definition API
  • Includes the Shadow DOM polyfill by default
  • Contains built-in data binding features

Libraries and Frameworks



Libraries and Frameworks

  function executeTarget(e){
    var targets = xtag.query(document, this.target);
    var method = this.method;
    var event = this.event;
    (targets[0] ? targets : [this]).forEach(function(target){
      if (typeof target[method] === 'function'){
      if (event) xtag.fireEvent(target, event);

  xtag.register('x-action', {
    events: {
      tap: executeTarget
    accessors: {
      target: { attribute: {} },
      method: { attribute: {} },
      event: { attribute: {} }
    methods: {
      execute: executeTarget
<polymer-element name="polymer-action"
                 attributes="target method event">
      function executeTarget(e){
        var targets = Array.prototype.slice.call(
        var method = this.method;
        var event = this.event;
        (targets[0] ? targets : [this]).forEach(function(target){
          if (typeof target[method] === 'function'){
          if (event) {
                new CustomEvent(event)
        created: function(){
          this.addEventListener('tap', executeTarget);
        execute: executeTarget

Where to learn, code, and contribute:

The Code-Off

"This will be a straight code-off, old school rules." - David Bowie

Challenge 1: Meme Tag

Description: Build a tag that displays top and bottom text over a meme picture


  • The image and top/bottom text should be dynamically configurable via attributes

Extra Credit:

  • Leverage a meme API to allow for grabbing meme pics on-demand

Challenge 2: Photo Stack

Description: Build a tag that arranges image elements in an organically uneven stack appearance 


  • Enable cycling through the stack on click and touch with an animated transition
  • Handle new image elements being added dynamically

Extra Credit:

  • Allow the order of the photo stacked images to be reversed using a boolean attribute 

Challenge 3: Comment Feed

Description: Create a comment feed element that supports custom child elements for comments/replies


  • Use custom elements for the main feed element, and the comment/reply elements it contains
  • Store the page's comment data so it repopulates the feed on refresh
  • Support common actions used in comments/replies: edit, delete, up-vote.

Extra Credit:

  • Sync your comment data to a remote source that works regardless of the containing page's domain


By Daniel Buchner


  • 2,031
Loading comments...

More from Daniel Buchner