Signal-driven • ~15KB • No build step

⚡ Signet.js

Add interactivity to plain HTML with js- directives. Fine-grained signal reactivity, zero build step, CSP-safe.

Get Started → See Examples GitHub ↗
Quick start — no JavaScript required
<script src="https://unpkg.com/signet.js" defer init></script>

<div js-scope="{ count: 0 }">
  <p js-text="'Count: ' + count"></p>
  <button js-on:click="count++">Increment</button>
</div>
⚡

Fine-Grained Reactivity

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

🔒

CSP-Safe

The default entry point uses a recursive descent parser. No eval() or new Function(). Safe under strict Content-Security-Policy headers.

ðŸŠķ

~15KB Bundle

Around 15KB minified (~5KB gzip) including @preact/signals-core. An alternate signet.js/unsafe entry point skips the CSP parser for an even smaller footprint.

ðŸ§ą

HTML-First

Add js-text, js-on:click, js-if and others to existing HTML. No templates, no compilation, no framework CLI.

ðŸŦ§

Zero Footprint

All js- attributes are stripped from the DOM after binding. The rendered output is clean, semantic HTML with no framework residue.

🔗

Composable Scopes

Nest js-scope elements to create local state. Child scopes inherit parent properties via JavaScript's prototype chain.

Why Signet.js?

Just Enough Framework

Alpine.js, petite-vue, and Stimulus are excellent tools. Signet.js is for when you need a handful of interactive elements in an otherwise server-rendered page — without a build pipeline, without learning a component model.

Signals, Not VDOM

Instead of diffing a virtual DOM tree on every state change, Signet.js uses fine-grained signals from @preact/signals-core. Only the DOM nodes that read a changed signal are updated, precisely and automatically.

Read the Source

The library is written in plain, readable ESM modules with no magic you can't understand in an afternoon. Pull it apart, fork it, or embed it directly — it's just JavaScript.

CDN — No JavaScript

The init attribute triggers auto-discovery of all top-level js-scope elements. No JavaScript required.

<!-- Auto-initialises all js-scope elements -->
<script src="https://unpkg.com/signet.js"
  defer init></script>

<div js-scope="{ name: '' }">
  <input js-model="name"
    placeholder="Your name" />
  <p js-show="name.length > 0"
    js-text="'Hello, ' + name + '!'">
  </p>
</div>

ESM — Full Control

Use createApp() when you need computed properties, methods, custom directives, or a global store.

import { createApp } from 'signet.js';

createApp({
  name: '',
  get greeting() {
    return this.name
      ? 'Hello, ' + this.name + '!'
      : '';
  },
  submit() {
    alert(this.greeting);
  },
})
  .mount('#app');
Read the Getting Started guide →

Explore the Docs