@avento-space/ts-ui
v1.1.1
Published
Framework-agnostic Web Components UI library with signals. Drop-in UI for React, Vue, Angular — zero adapters needed.
Maintainers
Readme
Avento TS UI
Framework-agnostic, signal-driven UI library for TypeScript.
Build reactive user interfaces once and render them anywhere — DOM, SSR, or custom targets — without coupling your components to any specific framework, browser API, or runtime environment.
Philosophy
Avento TS UI is built on a strict separation of concerns:
- Core — abstract UI model (VNodes), reactive primitives (signals/computed/effects), context, store, lifecycle, and plugin system. Zero platform dependencies.
- Render — generic
Renderer<T, THost>interface that decouples UI definition from output strategy. - Adapters — platform-specific renderers (DOM with reconciliation and hydration, SSR with streaming).
- Components — pure functional components consuming
(props, children?) => VNode, fully portable across all adapters.
No layer depends on the internals of another. The same component function works unchanged in the browser and on the server.
Features
- Signal-based reactivity — fine-grained, dependency-tracked, auto-batched updates
- Computed values — lazy, cached, dependency-aware derived state
- Pure functional components — no classes, no
this, no hidden state - Pluggable renderers — DOM (with reconciliation + hydration), SSR (string + streaming)
- Context system — typed provider chain for dependency injection
- Immutable store — subscription-based state management built on signals
- Lifecycle hooks —
onMount,onUnmount,onUpdate(enabled by renderer) - Plugin architecture — named hooks for extensibility (router, devtools, etc.)
- Fragment support — group children without extra DOM nodes
- JSX transform —
jsx/jsxs/jsxDEVfunctions for compiler integration - Strong TypeScript — full type inference for signals, components, and renderers
- Zero runtime dependencies — only TypeScript
Quick Start
Install
npm install @avento/ts-uiDOM Rendering
import { h, signal, computed, effect, mount } from '@avento/ts-ui';
const count = signal(0);
const doubled = computed(() => count.get() * 2);
function App() {
return h('div', { class: 'app' }, [
h('h1', null, ['Avento Counter']),
h('p', null, [`Count: `, count]),
h('p', null, [`Doubled: `, doubled]),
h('button', { onClick: () => count.set(count.get() + 1) }, ['+']),
h('button', { onClick: () => count.set(count.get() - 1) }, ['-']),
]);
}
const renderer = mount(h(App), document.getElementById('root')!);Signal values passed directly in children or props are automatically bound — the DOM updates reactively without re-rendering the entire tree.
SSR Rendering
import { h, renderToString } from '@avento/ts-ui';
function Page() {
return h('div', { class: 'page' }, [
h('h1', null, ['Hello from SSR']),
h('p', null, ['This was rendered on the server.']),
]);
}
const html = renderToString(h(Page));
console.log(html);
// <div class="page"><h1>Hello from SSR</h1><p>This was rendered on the server.</p></div>Architecture
src/
├── core/ # VNode, Signal, Component, Context, Store, Lifecycle, Plugin
├── render/ # Renderer<TOutput,THost> interface + VNode resolver
├── adapters/ # Platform renderers
├── adapters/ # Platform renderers
│ ├── dom/ # mount(), hydrate(), signal DOM bindings
│ └── ssr/ # renderToString(), renderToStream()
├── components/ # Reusable components (Button, Input, List, Portal)
├── plugins/ # Router plugin + plugin types
├── compiler/ # JSX transform (jsx/jsxs/jsxDEV)
└── devtools/ # VNode inspector, state loggerDependency Flow
Components ──► Core ◄── Plugins
│
Render Interface
│
┌──────────┼──────────┐
│ │ │
DOM SSR Custom AdapterCore never imports from adapters. Adapters import Core. Components import only Core types and the h factory.
Core Concepts
VNode
The universal UI representation:
interface VNode {
type: string | Component | typeof Fragment;
props: Record<string, any> | null;
children: VNodeChild[];
key?: string | number;
}h() — Hyperscript
h('div', { class: 'container' }, [
h('h1', null, ['Title']),
h('p', null, ['Paragraph with ', signalValue]),
]);type— element tag name, component function, orFragmentprops— attributes, properties, event listeners (onClick, etc.), signalschildren— VNodes, strings, numbers, booleans,null, or signals
Signals
import { signal, computed, effect, batch, untrack } from '@avento/ts-ui';
const name = signal('World');
const greeting = computed(() => `Hello, ${name.get()}!`);
effect(() => {
console.log(greeting.get()); // logs whenever name changes
});
batch(() => {
name.set('Agnostic');
name.set('UI'); // only one notification after batch
});Context
import { createContext, useContext, h, mount } from '@avento/ts-ui';
const Theme = createContext('light');
function Panel() {
const theme = useContext(Theme);
return h('div', { class: `theme-${theme}` }, ['Themed content']);
}Browser DOM
import { mount } from '@avento/ts-ui';
const instance = mount(h(App), document.getElementById('root')!);
// instance.render(newVNode) — re-render
// instance.hydrate(vnode) — attach to existing DOM
// instance.destroy() — unmount and clean upSignal values in props or children create direct DOM bindings — only the affected nodes update when the signal changes.
Server-Side Rendering
import { renderToString, renderToStream } from '@avento/ts-ui';
// Full document
const html = renderToString(h(Page));
// Streaming
for await (const chunk of renderToStream(h(Page))) {
res.write(chunk);
}Signals are resolved at render time via untrack(). Event handlers and other DOM-only props are omitted from the HTML output.
Built-in Components
Avento TS UI provides a set of highly optimized, platform-agnostic UI components:
- Button: Custom styled interactive button.
- Input: Reactive text input control with controlled states.
- List: Keyed child element list wrapper.
- VirtualList: Zero-overhead windowed list virtualization (scrolling requires external scroll bindings).
- Portal: Renders children outside the standard DOM hierarchy.
For complete API details and scroll state implementation examples, see the Components Documentation.
Plugins
import { registerPlugin, createRouter } from '@avento/ts-ui';
const router = createRouter({ mode: 'history' });
registerPlugin(router);
router.navigate('/dashboard');The plugin system uses named hooks. Custom plugins receive a PluginAPI to register and invoke hooks.
JSX Support
Configure your TypeScript tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@avento/ts-ui"
}
}The compiler module exports jsx, jsxs, and jsxDEV for the automatic JSX runtime.
Project Structure
| Layer | Directory | Responsibility |
|-------|-----------|----------------|
| Core | src/core/ | VNode, signals, context, store, lifecycle, plugins |
| Render | src/render/ | Renderer interface, VNode resolver |
| DOM Adapter | src/adapters/dom/ | mount(), hydrate(), signal bindings |
| SSR Adapter | src/adapters/ssr/ | renderToString(), renderToStream() |
| Components | src/components/ | Button, Input, List, Portal |
| Plugins | src/plugins/ | Router plugin |
| Compiler | src/compiler/ | JSX transform functions |
| DevTools | src/devtools/ | Inspector, state logger |
License
MIT
