npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

shadowroot-injector

v2.0.0

Published

🪡 Library to declaratively define shadowroots to repeat in HTML templates

Readme

ShadowRoot Injector

🪡 declaratively define shadowroots to repeat in HTML templates

What is ShadowRoot Injector?

ShadowRoot Injector lets you define templates for elements using HTML. When those elements appear in the DOM, the library will automatically insert a template into the element. You can then, optionally, upgrade the element using native web-component definitions (either inline in a script tag, or imported as a separate component definition).

Example

The example below shows a markdown callout. You can see a running example live on codepen.

<!-- 1. Auto-start the injector -->
<script src="https://unpkg.com/shadowroot-injector@2"></script>

<!-- 2. Define a generic <linked-header> -->
<shadowroot-for selector="callout-alert" mode="open">
  <template>
    <style>
      :host,
      slot {
        display: block;
      }

      details {
        display: block;
        border-left: solid 3px rgb(var(--callout-color));
        background: rgba(var(--callout-color), 0.1);
        padding: 0.5em;
      }

      summary {
        font-weight: bold;
        color: rgb(var(--callout-color));
        list-style: none;
      }
    </style>

    <details open>
      <summary><slot name="title"></slot></summary>
      <slot></slot>
    </details>
  </template>

  <!-- 3. Use it anywhere -->
  <p>ShadowRoot Injector lets you repeat templates easily, no JS required!</p>

  <callout-alert style="--callout-color: 160, 40, 40;">
    <span slot="title">Pro Tip!</span>
    If you want to add more behavior, you can upgrade custom-elements into web-components any time with JavaScript!
  </callout-alert>

  <p>You can check out the repository on Github.</p>

  <callout-alert style="--callout-color: 40, 40, 160;">
    <span slot="title">PRs Welcome!</span>
    You can make git issues or pull requests for any issues you find.
  </callout-alert></shadowroot-for
>

Why?

Today, there isn't a native or elegant way to repeat HTML content across the document without building a javascript component definition. For many simple authoring use-cases, just having a template that should appear is all that web authors need. This library gives you an easy and elegant way to do that, without the boilerplate or complexities associated with building javascript class definitions.

How to use

You can include ShadowRoot Injector by using a CDN of your choice.

<script src="https://unpkg.com/shadowroot-injector@2"></script>

You can also use the minified version by pointing to the minified asset

<script src="https://unpkg.com/shadowroot-injector@2/shadowroot-injector.min.js"></script>

HTML API

The HTML API is completely driven by attributes on the <shadowroot-for> web component. When you include both of the following attributes, ShadowRoot Injector will automatically kick off and register a child template node to be used for new and existing elements in the document.

You may also include any valid ShadowRoot template properties on the child template element. This includes shadowrootdelegatesfocus, shadowrootclonable, or even shadowrootcustomelementregistry.

JS API

While not required, you can use the JavaScript API to interface directly with a ShadowRoot Injector instance. This can also be useful when you need to control when the shadow root is injected in defined web components.

All the API methods below are method calls you can make on an instance of the ShadowRootInjector class.

<shadowroot-for id="injector" mode="open"> ... </shadowroot-for>
<div id="myTestNode"></div>

<script>
  injector.injectRegisteredTemplate(myTestNode);
</script>

Task List Example

To see these APIs come together, lets look at a more complex Task List example, step by step (you can see the entire file in example/task-list.html). You can see it live on codepen.

First, we'll import the library and build a template definition for a single task-item. It has some styles, and some basic markup.

<script src="https://unpkg.com/shadowroot-injector@2"></script>

<shadowroot-for selector="task-item" mode="open">
  <template>
    <style>
      :host {
        display: list-item;
      }
      li {
        display: flex;
        gap: 12px;
      }
    </style>
    <li>
      <slot></slot>
      <button>remove</button>
    </li>
  </template>
</shadowroot-for>

We'll create a list to hold some hard-coded task items.

<ul id="taskList">
  <task-item>Add Items</task-item>
  <task-item>Remove Items</task-item>
</ul>

If we stopped here, the task items would present as we'd expect, but wouldn't be interactive. To make it interactive, we'll upgrade our task-item custom element into a web component, with event listeners and all. Any existing task-item elements in the page will upgrade automatically.

[!important] In the connectedCallback, we call shadowRootInjector.injectRegisteredTemplate(this);. By doing this, we'll ensure that we have access to shadowRoot elements for the rest of the function.

<script>
  const taskItemShadowRootInjector = document.querySelector('shadowroot-for[selector="task-item"]');

  customElements.define(
    'task-item',
    class extends HTMLElement {
      connectedCallback() {
        taskItemShadowRootInjector.injectRegisteredTemplate(this);
        this.shadowRoot.querySelector('button').addEventListener('click', () => {
          this.remove();
        });
      }
    },
  );
</script>

Finally we add a control to create new task-item elements. Any new task-items created will be defined by the class definition above.

[!note] If we hadn't called taskItemShadowRootInjector.injectRegisteredTemplate directly, the ShadowRootInjector library would still inject shadowRoot templates after the element was attached to the page.

<label>
  Add Task
  <input id="addTaskInput" type="text" />
</label>

<script>
  addTaskInput.addEventListener('keyup', (event) => {
    if (event.code === 'Enter') {
      const newListItem = document.createElement('task-item');
      newListItem.textContent = addTaskInput.value;
      addTaskInput.value = '';
      taskList.append(newListItem);
    }
  });
</script>

Contributions / Discussions

If you think this is useful or interesting, I'd love to hear your thoughts! Feel free to reach out to me on mastodon, or join the Tram-One discord.