Async UI Dom runtime (experimental) .Designed for predictability over comfort — which, in practice, creates comfort. No Virtual DOM. No content diffing. Instead: real mutation, async components, deterministic structural updates.
Zero dependencies. Fully TypeScript.
- Async components (
awaitdirectly inside components) - Deterministic rendering without a Virtual DOM
signal/effectas minimal reactivity primitives- Explicit lifecycle (
mount,unmount,destroy) - Keyed
Forwith stable DOM identity - Structural conditional rendering (
Show) - Mutation is allowed (by design)
- Fully TypeScript
-
Async components are first-class
async function User() { const data = await fetch("/api/user").then((r) => r.json()); return <div>{data.name}</div>; }
-
Signals are active sources
- no reference equality checks
- mutation is allowed
- every
set()reliably triggers effects
-
Lifecycle is bound to real DOM nodes
mount/unmountattach to actual elements- for: reordering ≠ remounting
- removal = real
destroy
import { jsx, render, signal } from "../core";
import { For } from "../core/for";
const [items, setItems] = signal([
{ uuid: "a", name: "A" },
{ uuid: "b", name: "B" },
]);
render(<App />, { parent: document.body });
function App() {
return (
<ul>
<For each={items}>{(item) => <li>{item.name}</li>}</For>
</ul>
);
}Signals are active state containers, not passive values.
const [count, setCount] = signal(0);
setCount((v) => v + 1);
setCount(() => 42);set()always triggers- no equality checks
- mutation is allowed
- async setters are allowed
Subscribes to a signal and reacts to changes.
effect(count, (value) => {
console.log(value);
});- runs immediately with the current value
- runs on every
set() - no dependency tracking
- async callbacks supported
effect(userId, async (id) => {
const user = await fetch(`/api/user/${id}`).then((r) => r.json());
});## Context Injection
`render()` accepts arbitrary values on the top-level context.
This context is automatically available in **every component** via `props.ctx`.
```ts
render(<App />, {
parent: document.body,
api,
events,
dummy: 42,
});
```function Item(p: any) {
console.log(p.ctx.dummy); // 42
p.ctx.api.fetch();
}- no providers
- no hooks
- no imports
- no reactivity
Context is for stable infrastructure (stores, APIs, event buses), not for frequently changing UI state.
The context is immutable for the lifetime of the render tree and does not trigger re-renders.
Lifecycle is explicit and structural.
Registers a callback that runs once, when the component’s root DOM element is attached.
mount((parent) => {
// parent === root Element
});- runs exactly once per component instance
- runs after the DOM node exists
- used for subscriptions, timers, imperative DOM work
Registers cleanup logic tied to the component’s root element.
unmount(() => {
// cleanup
});- runs exactly once
- runs before DOM removal
- children unmount before parents
- guaranteed execution
For is not a general iterator.
It is a keyed structural renderer.
eachmust bet an array- each item must be an object
- each object must have a stable key field (e.g.
uuid) - no fallbacks, no warnings
type Item = {
uuid: string; // required
[key: string]: any;
};-
detects:
- order changes
- added items
- removed items
-
performs:
- DOM moves (
insertBefore) - rendering only for new keys
destroy()for removed keys
- DOM moves (
- no content diffing
- no re-rendering existing items
- no prop patching
- no heuristic matching
If an item’s content changes, the item itself must be reactive.
setItems((prev) => {
prev.push({ uuid: "c", name: "C" });
return prev;
});This is correct.
Why:
signalis active- effects are not reference-based
Forevaluates only keys and order
Show controls existence, not visibility.
<Show when={visible}>
<User />
</Show>