- Tiny. Core is under 2.5 KB. Optional
nanotags/contextandnanotags/renderentry points add ~400 B each, only if you import them. - Reactive props. Every prop is a Nano Stores atom, validated and coerced. Two-way sync between DOM attributes and state with no manual wiring.
- Typed refs and events.
r.one("button")infersHTMLButtonElement.ctx.onis typed against the correct event map for each target. Custom events are typed end-to-end. - Automatic cleanup. Listeners, effects, and bindings registered through
ctxare removed on disconnect. NodisconnectedCallbackboilerplate. - Hydration-first. Designed for static-first stacks like Astro: the markup is already on the page, nanotags just wires up behavior.
- No Shadow DOM, no template engine. Markup stays in regular DOM, styled with normal CSS.
<my-counter count="0">
<span data-ref="display">0</span>
<button data-ref="button">+1</button>
</my-counter>import { define } from "nanotags"
define("my-counter")
.withProps(p => ({ count: p.number(0) }))
.withRefs(r => ({ display: r.one("span"), button: r.one("button") }))
.setup(ctx => {
ctx.on(ctx.refs.button, "click", () => {
ctx.props.$count.set(ctx.props.$count.get() + 1)
})
ctx.effect(ctx.props.$count, val => {
ctx.refs.display.textContent = String(val)
})
})Documentation, interactive examples, and an llms.txt for AI assistants are at nanotags.psdcoder.dev.


