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).
- Add this script in the
<head>
:
<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>
- Create a
<template>
element with anid
. This will be part of the shadow DOM, so you can use things like slots and encapsulated styles here.
<template id="my-component">
<style>
:host {
display: block;
border: solid;
}
</style>
<p>This is inside the shadow tree.</p>
<slot></slot>
</template>
- Now inside the element where this shadow tree should be attached, add the
<use-html>
element, and reference the template’s id in thehref
attribute. Then do it multiple times (which is the whole point after all).
<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.
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.
<use-html href="#my-component"></use-html>
<use-html href="#my-component">
<p>This gets slotted from the outside</p>
</use-html>
Update (June 06): Please don’t use-html.