Mayank

The use-html custom element

Here’s a teeny-tiny custom element that allows declaratively using shadow DOM (similar to HTML’s built-in declarative shadow DOM) without repeating the template for each instance. This is inspired by the SVG <use> element, except it deliberately only handles templates located in the same document (avoiding asynchronous network requests).

  1. Add this script in the <head>:
Code snippet
<script>
  customElements.define(
    "use-html",
    class extends HTMLElement {
      connectedCallback() {
        const href = this.getAttribute("href");
        const template = this.ownerDocument.querySelector(href);
        if (!(template instanceof HTMLTemplateElement)) throw "oop";

        const mode = this.getAttribute("mode") || "open";
        const shadow = this.parentElement.attachShadow({ mode });
        shadow?.appendChild(template.content.cloneNode(true));

        this.remove();
      }
    }
  );
</script>
  1. Create a <template> element with an id. This will be part of the shadow DOM, so you can use things like slots and encapsulated styles here.
Code snippet
<template id="my-component">
  <style>
    :host {
      display: block;
      border: solid;
    }
  </style>
  <p>This is inside the shadow tree.</p>
  <slot></slot>
</template>
  1. Now inside the element where this shadow tree should be attached, add the <use-html> element, and reference the template’s id in the href attribute. Then do it multiple times (which is the whole point after all).
Code snippet
<my-component>
  <use-html href="#my-component"></use-html>
</my-component>

<my-component>
  <use-html href="#my-component"></use-html>
  <p>This gets slotted from the outside</p>
</my-component>

That’s it! Try it here. It works even in browsers that don’t support declarative shadow DOM.

Now tell me why this isn’t built into the browser.


Here’s another version. This one has nothing to do with declarative shadow DOM and is even more similar to the SVG <use> element. It’s like an HTML include.

Code snippet
customElements.define(
  "use-html",
  class extends HTMLElement {
    connectedCallback() {
      const href = this.getAttribute("href");
      const template = this.ownerDocument.querySelector(href);
      if (!(template instanceof HTMLTemplateElement)) throw "oop";

      const mode = this.getAttribute("mode") || "open";
      const shadow = this.attachShadow({ mode });
      shadow?.appendChild(template.content.cloneNode(true));
    }
  }
);

Instead of attaching a shadow-tree to a parent element, this attaches a shadow tree to itself, so it can be used in a more resilient, standalone capacity.

Code snippet
<use-html href="#my-component"></use-html>

<use-html href="#my-component">
  <p>This gets slotted from the outside</p>
</use-html>

Try it here.


Update (June 06): Please don’t use-html.