The Template Tag: A Refresher

The only remaining browser has begun to implement the template tag. With full cross-browser support in the near future, it’s time for a template tag refresher.

The <template> specification was introduced in 2011, along with the other three specs that make up WebComponents. Since then, <template> has become a part of the W3C Living Document, and has full support in Chrome, Firefox, Opera, Safari, and Android. To boot, Edge has just announced upcoming support.

In light of that announcement, let’s refresh (:

Native Templates

Before the <template> tag was introduced we still had templates, but to get them to work, we had to abuse existing functionality. The two most common techniques were:

  1. Hide the DOM off screen ```html

<ol start="2">
	<li>Abusing the <code>&lt;script&gt;</code> tag</li>
<script id="simple-template" type="text/x-handlebars-template">
  // My template's contents

Both of these techniques are not ideal. The first is clunky and can be accidentally modified by DOM manipulation. The latter exposes us to potential cross-site scripting attacks. The <template> element seeks to solve these issues, and also provides additional benefits.

The Anatomy of a Template

The anatomy of a template is simple. It is any set of markup, styles, and javascript that is wrapped in <template> tags. A template can even include another template! Here is a basic example:

<template id="simple">
        span { color: purple; }

    <img src="" />
    <span>Hello World!</span>

        function boom(){

The Properties of a Template

Flexible placement

This code can live anywhere inside the <head> or <body>. It’s also important to note that you can place it as a child for a <select> or <table> element.


This means that the Javascript and CSS play no role in modifying the page. The code is not active until the template has been cloned and added to the page.

Hidden from CSS and Selectors

Your template’s content cannot be selected or modified with CSS or query selectors. This is to protect it from accidental changes, and for performance reasons.

But.. How do we use our template?

Chiggity-check this out:

function addSimple(){
    // Grab our template
    var t = document.querySelector('template#simple').content;

    // Optional -- Modify template

    // Clone and add
    var clone = document.importNode(t, true);

Simple enough. We’re:

  • Grabbing a reference to our template’s content.
  • We create a deep-copy clone of the template’s content
  • and then we insert it into our existing DOM.

Simple Template Example

Notice that even though our span style is defined in our template, it doesn’t affect the existing span until it is cloned and added. Also, the JS is not executed until the template is added. Even the <image> isn’t fetched pre-load. Basically, adding the template is like a live copy & paste into your DOM.

Can I data-bind? Data-binding is fun!

Nope. The closest we can get to this is using DOM manipulation techniques to change our templates to create “fake” data-binding. Here is a slightly more intense example that demonstrates this:

<form id="data-binding-form" onsubmit="return addRow()">
    <label for="name-field">Name:</label>
    <input type="text" name="name" id="name-field" />

    <label for="age-field">Age:</label>
    <input type="text" name="age" id="age-field" />

    <label for="gender-field">Gender:</label>
    <input type="text" name="gender" id="gender-field" />

    <input type="submit" name="Submit" value="Submit"/>

<table id="persons-table">
    <td>Chester Examplefield</td>
<!-- Our Row Template -->
<template id="table-row">
<!-- End Row Template -->

Please Note:

  • Our template is inside our table
  • The template’s row is not visible on the screen
function addRow()
    // Grab our template
    var t = document.querySelector('template#table-row').content;

    // Optional -- Modify template
    var form = document.querySelector('#data-binding-form');

    var age = form.querySelector("#age-field").value; 		
    //var age = $("#data-binding-form #age-field"); // Equiv
    var name = form.querySelector("#name-field").value;
    var gender = form.querySelector("#gender-field").value;

    t.querySelector("td:first-child").innerHTML = name;
    t.querySelector("td:nth-child(2)").innerHTML = age;
    t.querySelector("td:last-child").innerHTML = gender;

    // Clone/activate template & add to page
    var clone = document.importNode(t, true);
    $("#persons-table tr:last").after(clone);   // .after() is using jQuery

    // Vanilla JS of .after();
    //var allRows = document.querySelectorAll("table tr");
    //var lastRow = allRows[allRows.length- 1];

    //lastRow.parentNode.insertBefore(clone, lastRow.nextSibling);

    return false; // Stop submit event from bubbling up

Please Note:

  • We grab a reference to the template’s content
  • We pull values from our form fields
  • We update the template’s content with our values
  • We clone the now modified template
  • We add the clone to the page

“Data-Binding” Example

You get the idea. Not super intuitive, but it works. This is likely where we’ll see abstractions like polymer pick up the slack and introduce more developer friendly ways to do data-binding.


Until we get full cross-browser support, it’s fairly safe to rely on Polyfills for <template> as they’re simple and performant. Here are some examples of polyfill use:

Even so, unless your target browser support is IE11+, you probably shouldn’t be using <template> in production. If anyone has any sources that show otherwise, please let me know! (:

Next Steps?

Templates lay the groundwork for the real meat and potatoes of WebComponents. To fully leverage their power, you may want to check out:



Questions from this reddit thread and comments below will eventually be aggregated here

© 2019. All rights reserved.