Introduction
An introduction to ilha, a tiny isomorphic UI library with signal reactivity and no virtual DOM.
ilha is a tiny, isomorphic UI library for building reactive components.
You render on the server and mount in the browser with signal-based updates. There is no virtual DOM and no compiler. Markup stays close to HTML.
What ilha is
A component is a function, async function, or generator that returns a view. The same component produces HTML through renderToString() and activates in the browser through mount().
You write a function component. You declare atom() values inside it. Reads during render subscribe that component; writes rerun it and morph the host.
Why it exists
Most UI stacks force you to choose between simplicity and interactivity. ilha keeps both close together: a small API, direct DOM updates, and one component for server and client.
This makes ilha a good fit when you want:
- Server-rendered markup.
- Small interactive regions.
- Explicit state.
- No virtual DOM.
How it feels to use
A typical component reads like a small HTML-aware module:
The same function can render to a string on the server and mount into the DOM on the client.
Choose the smallest component form
| Start with | Use it when |
|---|---|
const View = () => JSX |
Reusable markup inside a parent component |
const View = function* |
You need yield Stream.map(...) or yield* when(...) |
mount(el, View) |
The function is the root you hydrate or mount |
Start plain:
const const Status: () => ViewStatus = () => <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>Ready</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
Add local state with atom():
import { const atom: AtomFnatom } from "ilha";
const const Status: () => ViewStatus = () => {
const const message: AtomHandle<string>message = atom<string>(init: string | Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>): AtomHandle<string>atom("Ready");
return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{const message: AtomHandle<string>message}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
};
A nested function without its own mount() / renderToString() belongs to the parent component.
Core ideas
Isomorphic rendering
You produce HTML on the server and activate the same function in the browser.
Fine-grained reactivity
Atoms keep updates local. Changing one value does not rerender an entire application tree.
Function components
You write functions that return JSX. Generators yield streams and yield* Effects. Async functions can await before they return a view.
JSX-first authoring
Prefer JSX. It escapes interpolations. Use h() when you cannot use JSX.
When to use ilha
Use ilha when you want server HTML plus a few interactive regions, not a full client app by default.
Basic mental model
- Write a component that returns JSX.
- Declare
atom()values you read in the view. - Call
renderToString(component)on the server. - Call
mount(element, component, { hydrate: true })in the browser.