I have many instances of a custom element which uses a Shadow DOM. In their shadow DOM lives an <svg> element, which needs to display the same shapes with the same filters for every instance of the custom element.

In order to improve performance and simplicity, I'd like to define these shapes and filters only once and reuse them in every SVG element. I tried to define them in the main document, but the Shadow DOMs don't seem to be able to access them.

How can I reuse them? I cannot include them from a separate file, since I need to manipulate the shapes and filters via JavaScript.

Non-working example:

const svg = document.getElementById('prototype').innerHTML; // placing the <svg> within #regular document.getElementById('regular').innerHTML = svg; // placing the <svg> within the Shadow DOM of #shadow const host = document.getElementById('shadow'); const root = host.attachShadow({ mode: 'open' }); root.innerHTML = svg; div {border: 1px solid; display: inline-block;} <svg width="0" height="0"> <defs> <filter id="filter"> <feGaussianBlur stdDeviation=".03" in="SourceGraphic" edgeMode="none" result="blur"/> </filter> <path id="shape" d="M.5 0 L1 .5 L.5 1 L0 .5 Z" /> </defs> </svg> <div id="prototype" style="display:none"> <svg width="100" viewBox="0 0 1 1"> <use href="#shape" /> <text filter="url(#filter)" font-size=".7" font-weight="bold" font-family="sans-serif" fill="white" stroke="green" stroke-width=".04" x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">HI</text> </svg> </div> <div>Within the document:<span id="regular"></span></div> <div>Within a shadow DOM:<span id="shadow"></span>

Blue Nebula's user avatar

7

id=shape and use=#shape can not be used accross (user-defined) shadowRoots

If you want to create new elements from templates then:

use the <template> tag (content is NOT parsed by the browser!!)
containing your SVG content (and) create the elements with Web Components
creating your SVG content

Example <svg-shape> Web Component combining both patterns:

<template id="svg-shape-rect"> <svg> <rect x="25" y="25" width="50" height="50" fill="red" /> </svg> </template> <template id="svg-shape-circle"> <svg> <circle cx="50" cy="50" r="25" fill="yellow" /> <circle cx="50" cy="50" r="10" fill="rebeccapurple" /> </svg> </template> <style> svg-shape { width: 180px; background:beige } </style> <script> customElements.define('svg-shape', class extends HTMLElement { connectedCallback() { this.style.display = "inline-block"; // always display as inline-block or block (there is no default for custom elements) let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); // created with correct SVG namespace svg.setAttribute("viewBox", "0 0 100 100"); svg.innerHTML = `<circle cx="50" cy="50" r="40" fill="blue"></circle>`; // created via HTML parser sets correct namespace let includeShapes = (this.getAttribute('include') || "").split(','); includeShapes.forEach((attr, idx) => { let [shape, size = 50] = attr.trim().split(':'); let template = document.getElementById(this.localName + `-` + shape); if (template) { let clonedTemplate = template.content.cloneNode(true); // do not use the template ONCE! let svgNodes = Array.from(clonedTemplate.children[0].children); // get <svg> children, <svg> set correct namespace svg.append(...svgNodes); } }); this.attachShadow({ mode: 'open' }).append(svg); } }); </script> <svg-shape id="ONE" include="rect"></svg-shape> <svg-shape id="TWO" include="rect,circle"></svg-shape> <svg-shape id="THREE" include="circle,circle"></svg-shape>

Pitfalls

Namespace! Namespace! SVG must be created with the correct Namespace "http://www.w3.org/2000/svg"
Nice-to-know: When you insert an <svg> String tag into an HTML document, the DOM parser WILL SET the correct SVG Namespace That is why the <template> contains that <svg> tag. Otherwise <rect> and <circle> would be HTMLUnknownElements.

For HTML Nodes .nodeName is always UPPERCASE, but in SVG Namespace it is lowercase!

In above code include="circle,circle" create overlapping circles!

Danny '365CSI' Engelman's user avatar

2 Comments

That only works for standalone graphics though. You can't compose a new graphic reusing the other symbols and e.g. apply a single viewBox over these, or a single filter like OP is trying to do. And it really doesn't work for OP's case. Every element is "cloned", and thus applying a change on one won't apply on the others. That's basically the same as having the markup stored in a JSON.

2025-11-26T00:16:33.16Z+00:00

The Web Component could set a Muation Observer on the <template> (or anything in the DOM)

2025-11-26T08:27:11.17Z+00:00

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.