Build fast UIs with zero magic.
Vani is a Web‑Standards‑first UI runtime with explicit updates, anchor‑owned subtrees, and a clear and predictable rendering model across SPA and static sites.
Web‑standard HTML props
Use real attributes, styles, and events—no special syntax.
JS-first, zero compiler
Just TypeScript functions and DOM APIs, plus an optional JSX adapter.
SPA, SSR, and SSG
Same runtime, multiple delivery modes with explicit control.
Features
Everything you need, nothing you don’t.
A minimal runtime with powerful primitives.
Explicit Updates
Only re-render when you call handle.update(). Predictable without magic.
Signals (optional)
Opt-in fine-grained updates with signal(), text(), and attr().
Subtree Ownership
Each component owns a DOM range. Updates never touch parents or siblings.
Runtime‑First
JS-first and transpiler-free, with an optional JSX adapter.
SSR + Hydration
Anchor‑based hydration with deterministic behavior and no heuristics.
Client‑Only Islands
Mark subtrees as clientOnly for interactive islands.
Async Components
Promise‑based components with fallbacks, still explicit.
Incremental Adoption
Mount Vani widgets inside existing apps without much effort.
ESM‑First Portability
Ships small ESM modules for browsers, Node, Deno, or Bun.
Principles
Explicit by design.
Vani keeps rendering local, deterministic, and easy to reason about.
SPA, SSR, SSG
Use renderToDOM for SPA, renderToString for SSR/SSG, hydrateToDOM for activation.
No Hidden Work
No diffing and no implicit reactivity by default. Signals are opt-in.
Leaf‑Only Updates
Rendering cost scales with subtree size, not app size.
DOM Is Yours
Access the rendered DOM subtree directly—no hidden layer between you and the DOM.
Simple Debugging
Stack traces and behavior are readable and obvious.
API
Tiny API surface, huge control.
Install
Choose your package manager:
Install skills
Supercharge your AI Agents with Vani skills:
Explicit updates
State changes only re-render when you call handle.update().
Count: 0
Signals (optional)
Fine-grained updates with signal(), text(), and attr().
Count: 0
JSX mode counter
Use JSX syntax with the Vani JSX runtime.
Count: 0
JSX inside JS-first
Render JSX components inside element-helper trees.
Mixed render:
Conditional rendering
Use normal control flow to show or hide UI based on state.
Forms with explicit submit
Input changes do not re-render until the user submits.
Keyed lists
Keep item identity stable and update explicitly.
Global state
Subscribe to shared state and update explicitly.
Global count: 0
Controlled inputs
Update on every keystroke while preserving focus.
Live: ...
SVG components
Import SVGs with ?vani and render them as components.
Client-only islands
Mark islands with clientOnly: true (verify by disabling JS and reloading).
Transitions
Defer non-urgent UI work to keep interactions snappy.
- apples
- oranges
- pears
- grapes
Async components
Return a Promise of a render function with a fallback.
DOM refs
Access DOM nodes and control focus explicitly.
Value: ...
