ilha can be installed with your package manager of choice. For a beta project, start from a template when possible so SSR, mounting, and deployment wiring are already in place.
Install
Install with your package manager:
npm i ilhayarn add ilhapnpm add ilhabun add ilhaTemplates
If you want to start from a ready-made project instead of wiring everything manually, use one of the official templates.
| Template | Command | Sandbox |
|---|---|---|
| Vite SPA | npx giget@latest gh:ilhajs/ilha/templates/vite-spa |
Open |
| Nitro SSR | npx giget@latest gh:ilhajs/ilha/templates/nitro-ssr |
Open |
| Nitro + oRPC SPA | npx giget@latest gh:ilhajs/ilha/templates/nitro-orpc-spa |
Open |
Templates are the fastest way to get a working project structure for SSR, routing, and deployment targets without setting everything up from scratch.
Requirements
ilha is designed for modern JavaScript and TypeScript projects.
- Use it in apps that can run ESM modules.
- Use TypeScript if you want the best editor support.
- Use a browser environment for mounting and hydration.
- Keep props and hydration snapshots JSON-serializable when rendering on the server.
Import
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { const raw: (value: string) => RawHtmlraw, const css: (strings: TemplateStringsArray | string, ...values: (string | number)[]) => stringcss, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount, const from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | nullfrom, const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";Use the default export to create an island with ilha(() => JSX) or a configured builder chain. Use named exports for helpers such as raw and mount.
Minimal example
Create an island directly when it does not need builder configuration:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Greeting: Island<RootInput, RootState>Greeting = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Hello, ilha!</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);Start with a plain function for markup owned by another island, then wrap it with ilha() when it needs an independent lifecycle. Expand the shorthand into a builder chain when you need local state or actions:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Counter: Island<RootInput, MergeState<RootState, "count", number>>Counter = const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<number, "count">(key: "count", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>state("count", 0)
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>.action<"increment", undefined, void>(key: "increment", fn: (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"increment", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action("increment", (_: undefined_, { state: IslandState<MergeState<RootState, "count", number>>state }) => {
state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count((count: numbercount) => count: numbercount + 1);
})
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"increment", (props: undefined, ctx: ActionContext<...>) => void>>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"increment", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state, action: IslandActions<RootActions & Record<"increment", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action }) => (
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button onclick?: NativeEventHandler<PointerEvent & {
readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick={action: IslandActions<RootActions & Record<"increment", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action.increment: ActionAccessor<undefined, void>increment}>
Count: {state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count()}
</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
));Server-side rendering
Render the island to an HTML string with toString():
const const htmlOutput: stringhtmlOutput = const Counter: Island<RootInput, MergeState<RootState, "count", number>>Counter.Island<RootInput, MergeState<RootState, "count", number>>.toString(props?: Partial<RootInput> | undefined): stringtoString();If your island uses async derived values, you can also await the island itself:
const const htmlOutput: stringhtmlOutput = await const Counter: Island
(props?: Partial<RootInput> | undefined) => string | Promise<string>
Counter();Client-side mounting
Mount the island into a DOM element:
const const root: HTMLElement | nullroot = var document: Document**`window.document`** returns a reference to the document contained in the window.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.Document.getElementById(elementId: string): HTMLElement | nullThe **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.getElementById("app");
if (const root: HTMLElement | nullroot) {
const const unmount: () => voidunmount = const Counter: Island<RootInput, MergeState<RootState, "count", number>>Counter.Island<RootInput, MergeState<RootState, "count", number>>.mount(host: Element, props?: Partial<RootInput> | undefined): () => voidmount(const root: HTMLElementroot);
}The returned function stops listeners, effects, and other active behavior. Call it when removing the host element manually or when integrating ilha into another router.