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

@guifendjy/shadow-realm

v2.1.5

Published

A lightweight, reactive DOM framework built around a Signal-based reactivity system and a directive pipeline. Bind HTML attributes to JavaScript state — the DOM updates automatically whenever state changes. No virtual DOM, no build step required. Inspired

Readme

Shadow Realm

A lightweight, reactive DOM framework built around a Signal-based reactivity system and a directive pipeline. Bind HTML attributes to JavaScript state — the DOM updates automatically whenever state changes. No virtual DOM and no build step required. Inspired by Alpine.js.

Installation

npm install @guifendjy/shadow-realm
import Realm, { Shadow } from "@guifendjy/shadow-realm";

Quick Start

<div s-state="{ count: 0 }">
  <span s-text="count"></span>
  <button on:click="count++">Increment</button>
</div>

<script type="module">
  import Realm from "@guifendjy/shadow-realm";
  new Realm(document.getElementById("app")).initialize();
</script>

Table of Contents

  1. Realm
  2. Shadow Registry
  3. Declaring State
  4. Directives
  5. Event Listeners
  6. Helpers in Scope
  7. Stores
  8. Refs
  9. Plugins

Realm

The main reactive controller for a DOM subtree.

new Realm(root?, context?)

| Parameter | Type | Default | Description | | --------- | ---------------- | --------------- | -------------------------------------------- | | root | HTMLElement | document.body | Root element to make reactive | | context | Object \| null | null | Parent context to inherit from another Realm |

Methods

realm.initialize()Realm

Activates all bindings, attaches event listeners, and runs effects. Safe to call multiple times — subsequent calls are no-ops.

const realm = new Realm(document.querySelector("#app"));
realm.initialize();

realm.destroy()

Tears down the Realm: removes event listeners, unsubscribes all signal bindings, and runs effect cleanup functions. Call this before removing the element from the DOM.

realm.destroy();

Properties

| Property | Type | Description | | --------------- | ---------------- | -------------------------------------- | | realm.root | HTMLElement | The root DOM element | | realm.context | Object \| null | The injected parent context | | realm.ready | boolean | Whether initialize() has been called |


Shadow Registry

The Shadow export is the global registry for directives, plugins, and stores. It is shared across all Realm instances.

import { Shadow } from "@guifendjy/shadow-realm";

Shadow.directive(name, handler)

Registers a custom reactive directive. The name must start with "s-".

Shadow.directive("s-tooltip", ({ el, expression, execute }) => {
  el.title = execute(expression);
});

Handler arguments:

| Property | Description | | --------------- | -------------------------------------------------------- | | el | The DOM element the directive is on | | expression | The raw attribute value string | | value | The updated signal value (when a reactive token changed) | | context | The context the element is in. | | execute(expr) | Evaluates an expression string in the element's scope |

Using extendContext The extendContext method is a pure function: it does not modify the original context. Instead, it returns a new context object that includes your additional data. This is used specifically when you want to use the result of the expression within the template of the targeted node.

R.directive("s-**", ({ el, expression, execute, context }) => {
  // 1. Evaluate the expression provided in s-**="expression"
  const result = execute(expression);

  // 2. Create a NEW context that includes this result.
  // We extend the context so the child nodes can access '$match'
  const branchedContext = context.extendContext({ $match: result });

  // 3. Initialize the Realm with the extended context
  // The child template can now use s-text="$match"
  const realm = new Realm(node, branchedContext);
});

Shadow.use(pluginFn)Shadow

Registers a plugin. Chainable.

Shadow.use(MyPlugin).use(AnotherPlugin);

Shadow.store(name, callback)

Registers a global reactive store. See Stores.

Shadow.state(name, callback)

Registers a named, reusable state factory.

Shadow.state("counter", (start = 0) => ({ count: start }));

Declaring State

Use the s-state attribute to declare a reactive scope on any element. The value is a plain JavaScript object expression. It can include an init method that will be executed during the Realm's initialization.

<div s-state="{ name: 'Alice', count: 0, init(){...} }">
  <span s-text="name"></span>
</div>

Nested State

Child elements can declare their own s-state. They automatically inherit access to all ancestor state.

<div s-state="{ user: 'Alice' }">
  <div s-state="{ role: 'admin' }">
    <!-- Both `user` and `role` are accessible here -->
    <span s-text="user + ' — ' + role"></span>
  </div>
</div>

Reactivity and State Updates

When a state or store is defined via s-state, Shadow.state(...), or Shadow.store(...), the engine creates a reactive scope and converts every property into a Signal instance. That means the DOM is updated by signal notifications, not by mutating plain object/array properties in place.

  • s-state="{ count: 0 }" becomes an internal scope like { count: SignalInstance }
  • If a directive reads count, it subscribes to that exact count signal

Example:

// no DOM update
state.user.name = "Bob";

// triggers DOM update
state.user = { ...state.user, name: "Bob" };

Because the engine tracks dependency access through proxies, it knows exactly which signals each directive depends on. When a signal changes, it notifies only those subscribers and the DOM updates automatically. In the current implementation, state mutation must happen by replacing the signal value rather than modifying nested data in place. A future proxy layer may allow direct mutation to be translated into signal updates.

Directives

All directive attributes are removed from the DOM after processing.

s-text

Sets the textContent of an element.

<span s-text="name"></span> <span s-text="count + ' items'"></span>

s-value

Sets the value property of a form element.

<input s-value="searchQuery" />

s-show

Toggles visibility. Preserves the element's original display value and keeps aria-hidden in sync.

s-switch

The s-switch directive allows for conditional rendering of elements based on a matching expression. It functions similarly to a JavaScript switch statement, rendering only the branch that matches the provided value.

Usage: The directive must be placed on a tag. Inside that template, you define different branches using the case attribute. You can also define a fallback using the default attribute.

<div s-state="{ active: 0 }">
  <template s-switch="active">
    <div case="0">
      <!-- This content renders when active == 0 -->
      <button
        s-state="{ count: 0 }"
        on:click="count++"
        s-text="'Count is: ' + count"></button>
    </div>

    <div case="1">
      <p>Case 1 is active.</p>
    </div>

    <div case="2">
      <p>Case 2 is active.</p>
    </div>

    <div default>
      <p>This appears if active doesn't match 0, 1, or 2.</p>
    </div>
  </template>

  <button on:click="active++">Next Case</button>
</div>
<div s-show="isLoggedIn">Welcome!</div>

s-class

Dynamically adds/removes CSS classes. Accepts an object (class → boolean) or a string.

<!-- Object syntax -->
<div s-class="{ active: isActive, disabled: !isEnabled }"></div>

<!-- String syntax -->
<div s-class="currentTheme"></div>

s-style

Applies inline styles. Accepts an object (camelCase props) or a CSS string.

<!-- Object syntax -->
<div s-style="{ color: textColor, fontSize: '14px' }"></div>

<!-- String syntax -->
<div s-style="'color: red; font-weight: bold'"></div>

s-if

Conditionally mounts and unmounts a <template> element's content. The cloned content is initialized as a child Realm.

<template s-if="showModal">
  <div class="modal">...</div>
</template>

Must be used on a <template> element.

s-for

Renders a list from a <template>, using an LCS diff to minimize DOM operations on updates.

<template s-for="item in items">
  <div>
    <p s-text="item.name"></p>
    <p s-text="$index"></p>
  </div>
</template>

Override $index.

<template s-for="(item, indexName) in items">
  <div>
    <p s-text="item.name"></p>
    <p s-text="indexName"></p>
  </div>
</template>

Must be used on a <template> element.

s-effect

Runs an expression once as a side effect when the Realm mounts. The expression may return a cleanup function.

<div s-effect="initChart()"></div>

Custom effect directives can be registered using the s-effect-* naming pattern.

s-src

Safely sets the src attribute. Blocks javascript: protocol injection and supports Promises.

<img s-src="avatarUrl" />

s-disabled

Sets the disabled boolean attribute.

<button s-disabled="isLoading">Submit</button>

s-ref

Registers the element in the global $refs map.

<canvas s-ref="myCanvas"></canvas>

s-id

Sets the element's id attribute dynamically.

<section s-id="'section-' + index"></section>

s-scroll-text

Animates text changes with a vertical slide transition.

<span s-scroll-text="currentLabel"></span>

s-markdown

Renders a Markdown string as HTML. Requires marked to be loaded on the page.

<div s-markdown="markdownContent"></div>

Directive Quick Reference

| Attribute | Value | Reactive | Description | | --------------------- | ----------------- | -------- | -------------------------------------------------------------- | | s-state | JS object literal | — | Declares reactive state scope | | s-text | Expression | ✅ | Sets textContent | | s-value | Expression | ✅ | Sets .value | | s-show | Expression | ✅ | Toggles visibility | | s-switch | Expression | ✅ | renders elements based on a matching expression (<template>) | | s-class | Object or string | ✅ | Adds/removes classes | | s-style | Object or string | ✅ | Applies inline styles | | s-if | Expression | ✅ | Conditional render (<template>) | | s-for | item in list | ✅ | List render (<template>) | | s-effect | Expression | — | On-mount side effect | | s-effect-[modifier] | Expression | - | add a modifier to create custom side effect | | s-src | Expression | ✅ | Sets src safely | | s-disabled | Expression | ✅ | Sets disabled | | s-ref | Identifier | — | Registers in $refs | | s-id | Expression | ✅ | Sets id | | s-scroll-text | Expression | ✅ | Animated text swap | | s-markdown | Expression | ✅ | Renders Markdown | | on:[event] | Expression | — | DOM event listener |


Event Listeners

Attach DOM event listeners with on:[eventName].

<button on:click="count++">Click</button>
<input on:input="query = $event.target.value" />
<form on:submit="handleSubmit()"></form>

Special variables available in event expressions:

| Variable | Description | | --------- | ----------------------------- | | $event | The raw DOM Event object | | $target | Shorthand for $event.target |


Helpers in Scope

These are available inside any directive expression or event handler.

| Helper | Description | | -------------------------- | ------------------------------------------ | | $uniid(prefix?, length?) | Generates a unique ID | | $debounce(fn, delay?) | Returns a debounced version of a function | | $throttle(fn, delay?) | Returns a throttled version of a function | | $store | Access registered global stores | | $refs | Access registered element refs | | $event | Current DOM event (event handlers only) | | $target | Current event target (event handlers only) | | $index | Current loop index (inside s-for only) |

Standard globals such as console, Math, Date, JSON, and parseInt are also available.


Stores

Stores provide shared reactive state accessible from any Realm on the page. They support a built-in init method that runs during initialization.

Shadow.store("cart", () => ({
  items: [],
  total: 0,
  init() {
    console.log("Cart initialized");
  },
}));
Shadow.store("cart", () => ({
  items: [],
  total: 0,
}));

State declarations

State declarations provide a way to declare state outside of the template. They also support the built-in init method.

Shadow.state("cart", () => ({
  items: [],
  total: 0,
}));

Use it like this:

<div s-state="cart"></div>

Access in templates with $store.storeName.property:

<span s-text="$store.cart.total"></span>
<div s-show="$store.cart.items.length > 0">...</div>

Mutate from event handlers:

<button on:click="$store.cart.total = 0">Clear</button>

Refs

Refs provide direct access to DOM elements from within expressions.

<input s-ref="emailInput" />
<button on:click="$refs.emailInput.focus()">Focus Email</button>

Programmatic registration:

Shadow.$refs("emailInput", document.querySelector("#email"));

Plugins

A plugin is a function that receives the Shadow class and registers one or more directives.

function TooltipPlugin(Shadow) {
  Shadow.directive("s-tooltip", ({ el, expression, execute }) => {
    el.title = execute(expression);
  });
}

Shadow.use(TooltipPlugin);

Effect directives follow the s-effect-* naming convention and run during the effects phase (on mount):

function UpperCaseEffect(Shadow) {
  Shadow.directive("s-effect-upper", ({ el }) => {
    el.textContent = el.textContent.toUpperCase();
  });
}
<p s-effect-upper>hello world</p>
<!-- renders: HELLO WORLD -->

Contributing

Found a bug or have a feature suggestion? Feel free to open an issue, submit a pull request, or fork the repository.

License

MIT © 2025 Dads Guifendjy Paul