Comparison

How does Signet.js compare?

Honest trade-offs so you can pick the right tool for your project.

Strengths

✓ Tiny bundle

Around 15KB minified (~5KB gzip) including the reactive runtime from @preact/signals-core. No other dependencies.

✓ CSP-safe by default

The default entry point uses a recursive descent parser — no eval(), no new Function(). Works in strict CSP environments without unsafe-eval.

✓ No build step

Pure ESM. Drop a <script> tag or import as a module. No bundler, no compiler, no CLI.

✓ Fine-grained reactivity

Powered by @preact/signals-core. Only the exact DOM nodes that depend on a changed signal are updated — no re-rendering, no diffing.

✓ Zero footprint on the DOM

All js-* attributes are stripped from the DOM after binding. The HTML you ship to users is clean.

✓ HTML-first

Write interactivity in HTML attributes. No JavaScript required for simple use cases with defer init.

✓ Composable scopes

Nested js-scope elements form a prototype chain. Children inherit parent state; local changes don't pollute parent scope.

✓ Tree-shakeable

Module architecture lets bundlers tree-shake unused directives and the CSP-safe parser when using the unsafe entry point.

Limitations

⚠ Limited expression syntax

The CSP-safe parser doesn't support destructuring or multi-statement blocks. Complex logic should live in methods on your scope object. The signet-unsafe.js entry point accepts full JavaScript via new Function() if needed.

⚠ No components

Signet.js has no component model or slots. You can compose behaviors with nested scopes and custom directives, but it's not a replacement for component-based frameworks.

⚠ No route management

Single-page application routing is out of scope. Signet.js works per-page, not across navigations.

⚠ No SSR hydration (yet)

There is no dedicated hydration mode for server-rendered HTML. Mounting replaces children in js-if/js-for blocks.

⚠ Key-less list reconciliation

js-for re-creates all DOM nodes on every list change when the array length changes, and reuses nodes via in-place signal updates when it stays the same. No keyed diffing.

⚠ Smaller ecosystem

Compared to Alpine.js or petite-vue, there are fewer community plugins, examples, and integrations. This is a young library.

Comparison Table

Feature Signet.js Alpine.js petite-vue HTMX Stimulus
Bundle size ~5KB gz ~40KB (min+gz) ~6KB ~14KB ~11KB
Reactivity Signals Proxy (Vue-like) Proxy (Vue core) Server-driven AJAX DOM mutation callbacks
CSP-safe ✓ (default) ✗ (uses eval) ✗ (uses Function)
No build step Bundler recommended
Component model ✓ (x-component) Limited ✓ (Controllers)
Transitions / animation ✓ (x-transition) ✓ (v-show + CSS) CSS only CSS only
Template syntax js-* attrs x-* attrs v-* attrs hx-* attrs data-* attrs + JS
Server integration None None None ✓ (core feature) None
TypeScript types ✓ (JSDoc)
Ecosystem maturity New (2026) Mature Mature Mature Mature

When to choose Signet.js

You need fine-grained reactivity with minimal footprint

Signals mean only the exact DOM nodes that need an update are touched. No re-rendering, no dirty-checking, no full component re-evaluation.

Your environment has a strict Content Security Policy

Government sites, banking apps, enterprise intranets — anywhere unsafe-eval is forbidden. Signet.js default entry is the only HTML-directive library that works out of the box.

You want to add interactivity to server-rendered HTML without a build pipeline

Drop <script src="signet.js" defer init> in the <head>. Add js-scope to your HTML. You're done.

Bundle size is a hard constraint

E-commerce pages, landing pages, email-captured web views. Every kilobyte costs conversions. Signet.js + signals-core totals ~3KB.

You want to understand your tools completely

The entire source is <1000 lines of readable ESM. You can read it all in an afternoon. No magic, no abstractions hiding what's happening.

When not to choose Signet.js

You're building a complex SPA with routing and code-splitting

Use React, Solid, Svelte, or Vue. Signet.js has no component model, no router, and no state management beyond flat reactive scopes.

You need rich transitions and animation directives

Alpine.js's x-transition and Vue's transition system are excellent. Signet.js defers this to CSS.

You rely heavily on server-driven partial HTML swapping

HTMX is purpose-built for that. Signet.js manages local client-side state; it doesn't fetch HTML from a server.

Your team needs a large community / plugin ecosystem

Alpine.js has hundreds of plugins, Mastodon/Twitter communities, and years of Stack Overflow answers. Signet.js is new.

Alternatives Deep Dive

vs Alpine.js

Alpine.js is the most direct competitor. Both add directives to HTML and avoid a build step. The key differences:

  • Alpine.js uses eval() for expressions (requires CSP unsafe-eval). Signet.js does not.
  • Alpine.js is ~40KB (min+gz); Signet.js is ~5KB (min+gz). Both are drop-in.
  • Alpine.js has transitions, magic helpers ($store, $dispatch, $watch), and a plugin system. Signet.js is minimal.
  • Signet.js uses fine-grained signals; Alpine.js uses a mutation-tracking Proxy (similar to Vue's reactivity).

vs petite-vue

petite-vue is Vue's official lightweight drop-in. It uses the same v-* syntax as Vue 3:

  • petite-vue expressions are full JavaScript (via new Function()). Not CSP-safe.
  • petite-vue is ~6KB — larger than Signet.js but smaller than full Vue.
  • petite-vue supports some Vue component features (scoped components, v-scope).
  • If you're already on Vue, petite-vue is a natural fit. If not, the Vue syntax is another thing to learn.

vs HTMX

HTMX and Signet.js are complementary, not competing:

  • HTMX swaps server-rendered HTML fragments into the page. Signet.js manages local client-side state.
  • You can use both: HTMX to load content, Signet.js to make individual widgets interactive.
  • HTMX doesn't track reactive state. Signet.js doesn't make server requests.

vs Stimulus

Stimulus takes a more structured, class-based approach:

  • Stimulus connects controllers (ES6 classes) to DOM elements. Signet.js connects reactive scopes to DOM elements.
  • Stimulus is intentionally minimal in the DOM. Signet.js strips all its attributes (zero footprint).
  • Stimulus works best with a bundler (Webpack, esbuild). Signet.js is a single ESM file.
  • Stimulus has no built-in reactivity. You manually update the DOM from controller methods. Signet.js automates this via signals.