Skip to content

.render()

Finalize the builder chain and produce a callable island that can render JSX to HTML or mount in the browser.

Updated View as Markdown

Finalizes the builder chain and returns a callable Island. This is always the last method in the chain — every other builder method must be called before .render().

JSX setup

ilha ships its own JSX runtime. For TypeScript projects, set jsxImportSource to "ilha":

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "ilha"
  }
}

For individual examples or files, you can use a file pragma instead:

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 Hello: Island<RootInput, RootState>Hello = 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>);

JSX is the recommended authoring style for islands. The lower-level html`` helper is still available and is useful for no-build environments or places where you do not want JSX tooling. For example, you can drop a <script type="module"> into a plain index.html with an import map and author islands entirely with html`` — no build step or compiler required.

Choose the smallest component form

There are two runtime modes: a transparent plain component and an independent island. The island has a shorthand and a configured builder form.

Form Choose it when
const View = () => JSX You need reusable markup inside another island
const View = ilha(() => JSX) The component needs an independent mount, hydration, reactive scope, or event lifecycle
ilha.state(...).render(...) The island needs local state, derived values, actions, input, or lifecycle hooks

Start with a plain component

A plain function keeps markup reusable without creating another lifecycle boundary:

const 
const Label: ({ text }: {
    text: string;
}) => JSX.Element
Label
= ({ text: stringtext }: { text: stringtext: string }) => (
<"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>{text: stringtext}</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span> );

When an island renders <Label />, the containing island owns its event handlers, signal subscriptions, rerenders, and cleanup.

Promote it to an island

Wrap the render function with ilha() when the component needs independent ownership:

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 Label: Island<{
    text: string;
}, RootState>
Label
=
ilha<{
    text: string;
}>(fn: (ctx: RenderContext<{
    text: string;
}, RootState, RootDerived, RootActions>) => string | RawHtml): Island<{
    text: string;
}, RootState>
ilha
<{ text: stringtext: string }>(({
input: {
    text: string;
}
input
}) => (
<"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>{
input: {
    text: string;
}
input
.text: stringtext}</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>
));

ilha(renderFn) is shorthand for ilha.render(renderFn). It returns a complete Island with its own reactive scope and lifecycle, plus mount(), hydratable(), key(), and define().

Add local capabilities

Expand the shorthand into a builder chain when the island needs state, derived values, actions, input validation, effects, or other 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 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
>
));

Both forms return the same kind of Island; the builder only adds capabilities.

Render island output

Use .toString() when you need synchronous HTML:

Counter.toString(); // → <button>Count: 0</button>

Use await Counter() when the island may contain asynchronous derived values. Keeping the await explicit avoids accidentally handling a possible Promise<string> as a string.

Render context

The render function receives a RenderContext with everything declared in the builder chain:

{
  state: IslandState; // reactive state signals
  derived: IslandDerived; // derived signal accessors
  action: IslandActions; // callable actions and their reactive status
  input: TInput; // resolved input props
}

All four are always present, even if not declared. An island with no state gets an empty state object, and so on.

Return type

The render function returns JSX, a plain string, or a RawHtml object. In day-to-day code, prefer JSX:

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 } from "ilha";
// JSX — safe interpolation with auto-escaping const const A: Island<RootInput, RootState>A = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Hello</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>); // Plain string — okay for fully static markup const const B: Island<RootInput, RootState>B = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => "<p>hello</p>"); // raw() — trusted markup inside JSX const const C: Island<RootInput, RootState>C = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>{function raw(value: string): RawHtmlraw("<em>trusted</em>")}</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>);

Use JSX whenever you interpolate dynamic values. Plain strings do not escape interpolated values, so reserve them for static or already-safe markup.

Escaping and safe values

JSX children are escaped by default:

const const userInput: "<script>alert(\"xss\")</script>"userInput = '<script>alert("xss")</script>';

<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{const userInput: "<script>alert(\"xss\")</script>"userInput}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
// → <p>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</p>

null and undefined render as empty strings. Arrays are flattened and rendered without commas.

<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{["a", null, var undefinedundefined, "b"]}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>
// → <p>ab</p>

Use raw() only for trusted markup you control:

import { const raw: (value: string) => RawHtmlraw } from "ilha";

const const icon: "<svg aria-hidden=\"true\">…</svg>"icon = `<svg aria-hidden="true">…</svg>`;

<
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>{function raw(value: string): RawHtmlraw(const icon: "<svg aria-hidden=\"true\">…</svg>"icon)} Save</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>;

Signals in JSX

State entries are signal accessors. You can call them explicitly:

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>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{state: IslandState<MergeState<RootState, "count", number>>state.
count: MarkedSignalAccessor
() => number (+1 overload)
count
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);

You can also pass the accessor itself as a child; ilha will read it and escape the value:

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 Label: Island<RootInput, MergeState<RootState, "label", string>>Label =
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<string, "label">(key: "label", init?: StateInit<RootInput, string> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "label", string>, RootDerived, RootActions>state("label", "<b>safe</b>") .IlhaBuilder<RootInput, MergeState<RootState, "label", string>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "label", string>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "label", string>>render(({ state: IslandState<MergeState<RootState, "label", string>>state }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{state: IslandState<MergeState<RootState, "label", string>>state.label: SignalAccessor<string>label}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>); // → <p>&lt;b&gt;safe&lt;/b&gt;</p>

Attributes

JSX attributes are escaped. HTML boolean attributes render when true; false, null, and undefined omit them. ARIA values and enumerated attributes such as draggable serialize booleans as "true" or "false".

<
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
disabled?: boolean | undefineddisabled={true} title?: string | RawHtml | undefinedtitle={'a"b'}>
Save </
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
// → <button disabled title="a&quot;b">Save</button>

Ilha types each standard tag with its valid properties. Your editor completes input properties such as accept, checked, form, and list, but rejects unrelated properties such as href. Event handlers infer the element-specific currentTarget type.

<
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
accept?: string | RawHtml | undefinedaccept="image/*" form?: string | RawHtml | undefinedform="upload"
oninput?: NativeEventHandler<InputEvent & {
    readonly currentTarget: HTMLInputElement;
}> | undefined
oninput
={(
event: InputEvent & {
    readonly currentTarget: HTMLInputElement;
}
event
) => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(
event: InputEvent & {
    readonly currentTarget: HTMLInputElement;
}
event
.currentTarget: EventTarget & HTMLInputElement
The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)
currentTarget
.HTMLInputElement.value: string
The **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
)}
/>

ARIA attributes, data-*, CSS properties, common SVG attributes, and element-specific bind:* names also receive completion. Dashed custom elements accept custom properties and events.

Use class normally. className and htmlFor are also accepted and normalized to class and for.

<
"label": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLabelElement> & {
    for?: string;
    htmlFor?: string;
}>
label
htmlFor?: string | RawHtml | undefinedhtmlFor="email" class?: RawHtml | ClassValue | undefinedclass="field">
Email </
"label": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLabelElement> & {
    for?: string;
    htmlFor?: string;
}>
label
>

class also accepts arrays and object maps:

const const active: trueactive = true;

<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div class?: RawHtml | ClassValue | undefinedclass={["tab", const active: trueactive && "is-active"]}>Tab</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>;
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div class?: RawHtml | ClassValue | undefinedclass={{ tab: truetab: true, "is-active": const active: trueactive }}>Tab</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>;

Use lowercase native event props such as onclick={handler} and onchange={handler}. CamelCase props such as onClick are omitted.

Blocked and sanitized attributes

A few attribute forms are restricted for security, even when the value comes from a variable:

  • srcdoc and srcDoc are always dropped. They decode HTML entities back into live markup, so normal attribute escaping does not neutralize them — a bound srcdoc would be an XSS hole, so ilha omits the attribute regardless of its value.
  • URL attributes (href, src, action, formaction, cite, data, poster, and their namespaced equivalents like xlink:href) strip ASCII control characters before checking the scheme, so a value like "java\tscript:alert(1)" is still caught. Values matching a disallowed scheme (javascript:, vbscript:, data:text/html, and similar) are dropped rather than emitted.
  • A style object declaration is dropped whole if its value contains ;, {, }, <, >, expression(, or javascript: — these could smuggle extra declarations or markup. Quotes are allowed, so quoted fontFamily or content values work normally:
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div
  style?: string | RawHtml | StyleProps | undefinedstyle={{
    fontFamily?: string | number | null | undefined
The font-family CSS property specifies a prioritized list of one or more font family names and/or generic family names for the selected element. [MDN Reference](https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/font-family)
fontFamily
: '"Fira Code", monospace', // kept — quotes are fine
content?: string | number | null | undefined
The **`content`** CSS property replaces content with a generated value. It can be used to define what is rendered inside an element or pseudo-element. For elements, the content property specifies whether the element renders normally (normal or none) or is replaced with an image (and associated "alt" text). For pseudo-elements and margin boxes, content defines the content as images, text, both, or none, which determines whether the element renders at all. [MDN Reference](https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/content)
content
: '"hello"', // kept
}} > safe </"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>

Events and re-rendering

Use lowercase native event props for logic local to one element:

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>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state }) => (
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div> <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Count: {state: IslandState<MergeState<RootState, "count", number>>state.
count: MarkedSignalAccessor
() => number (+1 overload)
count
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>
<
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
type?: RawHtml | "button" | "reset" | "submit" | undefinedtype="button"
onclick?: NativeEventHandler<PointerEvent & {
    readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick
={() => state: IslandState<MergeState<RootState, "count", number>>state.
count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count
((count: numbercount) => count: numbercount + 1)}
> + </
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div> ));

The handler receives the native DOM event and a second context argument containing a lifecycle signal. This supports form events such as oninput, onchange, onselect, onsubmit, onreset, and oninvalid:

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 Form: Island<RootInput, RootState>Form = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form
onsubmit?: NativeEventHandler<SubmitEvent & {
    readonly currentTarget: HTMLFormElement;
}> | undefined
onsubmit
={(
event: SubmitEvent & {
    readonly currentTarget: HTMLFormElement;
}
event
) => {
event: SubmitEvent & {
    readonly currentTarget: HTMLFormElement;
}
event
.Event.preventDefault(): void
The **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)
preventDefault
();
}} > <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
onchange?: NativeEventHandler<Event & {
    readonly currentTarget: HTMLInputElement;
}> | undefined
onchange
={(
event: Event & {
    readonly currentTarget: HTMLInputElement;
}
event
) => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(
event: Event & {
    readonly currentTarget: HTMLInputElement;
}
event
.currentTarget: EventTarget & HTMLInputElement
The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)
currentTarget
)}
/> <
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
type?: RawHtml | "button" | "reset" | "submit" | undefinedtype="submit">Save</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
</"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form> ));

Ilha attaches handlers with addEventListener. It refreshes handler closures after each re-render and removes them when the island unmounts. The handler’s signal aborts when Ilha replaces its listener during a re-render or the island starts unmounting:

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 Search: Island<RootInput, RootState>Search = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
"oninput:abortable"?: NativeEventHandler<InputEvent & {
    readonly currentTarget: HTMLInputElement;
}> | undefined
oninput
:
"oninput:abortable"?: NativeEventHandler<InputEvent & {
    readonly currentTarget: HTMLInputElement;
}> | undefined
abortable
={async (
event: InputEvent & {
    readonly currentTarget: HTMLInputElement;
}
event
, { signal: AbortSignalsignal }) => {
const const input: HTMLInputElementinput =
event: InputEvent & {
    readonly currentTarget: HTMLInputElement;
}
event
.currentTarget: EventTarget & HTMLInputElement
The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)
currentTarget
as HTMLInputElement;
await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(`/api/search?q=${const input: HTMLInputElementinput.HTMLInputElement.value: string
The **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
}`, { RequestInit.signal?: AbortSignal | null | undefined
An AbortSignal to set request's signal.
signal
});
}} /> ));

Add one modifier after the event name:

Syntax Behavior
onclick:once={fn} Runs once, then removes the listener
onclick:capture={fn} Listens during the capture phase
onscroll:passive={fn} Registers a passive listener
oninput:abortable={fn} Aborts the previous invocation on the next input

Native event props accept one modifier. Functions never become inline HTML attributes during SSR.

Use .on() for advanced listeners

Prefer native event props when one rendered element owns the event. Use .on() when you need a CSS selector, an island-host listener, the full island handler context, or multiple modifiers.

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 Controls: Island<RootInput, MergeState<RootState, "count", number>>Controls =
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>.on<"[data-increment]@click:once:capture">(selectorOrCombined: "[data-increment]@click:once:capture", handler: (ctx: HandlerContextFor<RootInput, MergeState<RootState, "count", number>, "click", RootDerived, RootActions>) => void | Promise<void>): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>on("[data-increment]@click:once:capture", ({ 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>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state }) => ( <
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
data-increment: truedata-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
>
));

Use "@click" without a selector to listen on the island host. .on() handlers receive state, derived, action, input, host, target, event, and signal.

When state.count changes, only this island re-renders and morphs its host DOM.

List rendering

Arrays of JSX results are joined without commas. Do not call .join(""):

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 List: Island<RootInput, MergeState<RootState, "fruits", string[]>>List =
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<string[], "fruits">(key: "fruits", init?: StateInit<RootInput, string[]> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "fruits", string[]>, RootDerived, RootActions>state("fruits", ["apple", "banana", "cherry"]) .IlhaBuilder<RootInput, MergeState<RootState, "fruits", string[]>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "fruits", string[]>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "fruits", string[]>>render(({ state: IslandState<MergeState<RootState, "fruits", string[]>>state }) => ( <"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> {state: IslandState<MergeState<RootState, "fruits", string[]>>state.
fruits: MarkedSignalAccessor
() => string[] (+1 overload)
fruits
().Array<string>.map<JSX.Element>(callbackfn: (value: string, index: number, array: string[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((fruit: stringfruit) => (
<
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>{fruit: stringfruit}</
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>
))} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> ));

Each item is escaped independently, so mapped user content stays safe.

Keyed lists with data-key

Tag rendered list items with a data-key attribute to have the morph engine match elements by key and move them on reorder instead of rewriting content at each position:

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 List: Island<RootInput, MergeState<RootState, "order", string[]>>List =
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<string[], "order">(key: "order", init?: StateInit<RootInput, string[]> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "order", string[]>, RootDerived, RootActions>state("order", ["a", "b", "c"]) .IlhaBuilder<RootInput, MergeState<RootState, "order", string[]>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "order", string[]>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "order", string[]>>render(({ state: IslandState<MergeState<RootState, "order", string[]>>state }) => ( <"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> {state: IslandState<MergeState<RootState, "order", string[]>>state.
order: MarkedSignalAccessor
() => string[] (+1 overload)
order
().Array<string>.map<JSX.Element>(callbackfn: (value: string, index: number, array: string[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((k: stringk) => (
<
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
data-key: stringdata-key={k: stringk}>{k: stringk}</
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>
))} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> ));

When order changes to ["c", "a", "b"], the existing <li> elements are moved to their new positions rather than replaced — focus, selection, in-flight CSS transitions, and any imperative state you attached to the element (like a property set outside of ilha) survive the reorder. Removed keys unmount; new keys mount in place. Content without data-key keeps the previous positional-diff behavior.

Use data-key for plain rendered list items like this. For list items that are themselves child islands with their own local state, use Island.key() instead — it stabilizes the child island’s slot identity rather than a plain element.

Always key identity-sensitive elements. An <iframe>, <video>, <audio>, <canvas>, <embed>, or <object> that gets caught in a positional replace loses state that can’t be recreated — the iframe reloads its document, media restarts, canvas pixels vanish. Give such elements (or the list items containing them) a data-key so the morph preserves their identity; in development, ilha warns when a replace destroys one.

Controller-owned attributes. When outside code (a component controller, a third-party script) imperatively manages attributes on an element inside an island, list them in data-morph-preserve so re-renders don’t overwrite or strip them — class can be listed too:

<div
  data-widget
  data-morph-preserve="class data-open aria-expanded"
></div>

Fragments

Use fragments when a render function needs to return siblings without an extra wrapper:

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 Form: Island<RootInput, RootState>Form = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
name?: string | RawHtml | undefinedname="email" />
<
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>Submit</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
</> ));

Child islands

Render child islands as JSX components:

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 Child: Island<RootInput, MergeState<RootState, "count", number>>Child =
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>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state }) => <
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>{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
>);
const const Parent: Island<RootInput, RootState>Parent = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <"section": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>section> <"h1": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLHeadingElement>>h1>Parent</"h1": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLHeadingElement>>h1> <const Child: Island<RootInput, MergeState<RootState, "count", number>>Child /> </"section": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>section> ));

Child islands render inline during SSR and mount independently on the client. A state change in the child does not re-render the parent.

Pass props with normal JSX attributes:

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";
import { import zz } from "zod"; const
const Badge: Island<{
    label: string;
} & Record<string, unknown>, Record<never, never>>
Badge
=
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>.input<z.ZodObject<{
    label: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    label: z.ZodString;
}, z.core.$strip>): IlhaBuilder<{
    label: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input
(import zz.
function object<{
    label: z.ZodString;
}>(shape?: {
    label: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    label: z.ZodString;
}, z.core.$strip>
object
({ label: z.ZodStringlabel: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
IlhaBuilder<{ label: string; } & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
    label: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
    label: string;
} & Record<string, unknown>, Record<never, never>>
render
(({
input: {
    label: string;
} & Record<string, unknown>
input
}) => <"strong": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>strong>{
input: {
    label: string;
} & Record<string, unknown>
input
.label: stringlabel}</"strong": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>strong>);
const const Page: Island<RootInput, RootState>Page = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <
const Badge: Island<{
    label: string;
} & Record<string, unknown>, Record<never, never>>
Badge
label?: string | undefinedlabel="New" />);

For keyed child islands in lists, create a keyed component before rendering it:

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";
import { import zz } from "zod"; const
const items: {
    id: string;
    label: string;
}[]
items
= [
{ id: stringid: "a", label: stringlabel: "A" }, { id: stringid: "b", label: stringlabel: "B" }, ]; const
const Item: Island<{
    label: string;
} & Record<string, unknown>, Record<never, never>>
Item
=
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>.input<z.ZodObject<{
    label: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    label: z.ZodString;
}, z.core.$strip>): IlhaBuilder<{
    label: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input
(import zz.
function object<{
    label: z.ZodString;
}>(shape?: {
    label: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    label: z.ZodString;
}, z.core.$strip>
object
({ label: z.ZodStringlabel: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
IlhaBuilder<{ label: string; } & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
    label: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
    label: string;
} & Record<string, unknown>, Record<never, never>>
render
(({
input: {
    label: string;
} & Record<string, unknown>
input
}) => <
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>{
input: {
    label: string;
} & Record<string, unknown>
input
.label: stringlabel}</
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>);
const const List: Island<RootInput, RootState>List = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> {
const items: {
    id: string;
    label: string;
}[]
items
.
Array<{ id: string; label: string; }>.map<JSX.Element>(callbackfn: (value: {
    id: string;
    label: string;
}, index: number, array: {
    id: string;
    label: string;
}[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((
item: {
    id: string;
    label: string;
}
item
) => {
const
const KeyedItem: KeyedIsland<{
    label: string;
} & Record<string, unknown>>
KeyedItem
=
const Item: Island<{
    label: string;
} & Record<string, unknown>, Record<never, never>>
Item
.
Island<{ label: string; } & Record<string, unknown>, Record<never, never>>.key(key: string): KeyedIsland<{
    label: string;
} & Record<string, unknown>>
key
(
item: {
    id: string;
    label: string;
}
item
.id: stringid);
return <
const KeyedItem: KeyedIsland<{
    label: string;
} & Record<string, unknown>>
KeyedItem
label?: string | undefinedlabel={
item: {
    id: string;
    label: string;
}
item
.label: stringlabel} />;
})} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> ));

Slot wrapper — .as(tag)

Embedded child islands are wrapped in a host element so ilha can hydrate and morph them independently of the parent. By default that wrapper is a <div> with a data-ilha-slot attribute:

<div data-ilha-slot="p:0">…child markup…</div>

Call .as(tag) on the child island builder (anywhere before .render()) to use a different HTML tag — for valid structure (<li> inside <ul>), semantics, or styling:

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 Age: Island<RootInput, MergeState<RootState, "age", number>>Age =
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>.as<"span">(tag: "span"): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>as("span") .IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<number, "age">(key: "age", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "age", number>, RootDerived, RootActions>state("age", 0) .IlhaBuilder<RootInput, MergeState<RootState, "age", number>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "age", number>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "age", number>>render(({ state: IslandState<MergeState<RootState, "age", number>>state }) => <>{state: IslandState<MergeState<RootState, "age", number>>state.
age: MarkedSignalAccessor
() => number (+1 overload)
age
()}</>);
const const Parent: Island<RootInput, RootState>Parent = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Age: {const Age: Island<RootInput, MergeState<RootState, "age", number>>Age}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>); // → <p>Age: <span data-ilha-slot="p:0">0</span></p>
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 Row: Island<{
    label: string;
}, Record<never, never>>
Row
=
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>.as<"li">(tag: "li"): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>as("li") .
IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.input<{
    label: string;
}>(): IlhaBuilder<{
    label: string;
}, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input
<{ label: stringlabel: string }>()
.
IlhaBuilder<{ label: string; }, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
    label: string;
}, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
    label: string;
}, Record<never, never>>
render
(({
input: {
    label: string;
}
input
}) => <>{
input: {
    label: string;
}
input
.label: stringlabel}</>);
const const List: Island<RootInput, RootState>List = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> {["a", "b"].Array<string>.map<JSX.Element>(callbackfn: (value: string, index: number, array: string[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((label: stringlabel) => (
<
const Row: Island<{
    label: string;
}, Record<never, never>>
Row
JSX.IntrinsicAttributes.key?: string | number | undefinedkey={label: stringlabel} label?: string | undefinedlabel={label: stringlabel} />
))} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> ));

For list items that keep local state across reorders, combine .as("li") with .key(id) on the child (see keyed child islands above):

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 Item: Island<{
    label: string;
}, MergeState<Record<never, never>, "n", number>>
Item
=
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>.as<"li">(tag: "li"): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>as("li") .
IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.input<{
    label: string;
}>(): IlhaBuilder<{
    label: string;
}, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input
<{ label: stringlabel: string }>()
.
IlhaBuilder<{ label: string; }, Record<never, never>, Record<never, never>, Record<never, never>>.state<number, "n">(key: "n", init?: StateInit<{
    label: string;
}, number> | undefined): IlhaBuilder<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>, Record<never, never>>
state
("n", 0)
.
IlhaBuilder<{ label: string; }, MergeState<Record<never, never>, "n", number>, Record<never, never>, Record<never, never>>.action<"bump", undefined, void>(key: "bump", fn: (props: undefined, ctx: ActionContext<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>>) => void): IlhaBuilder<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>, Record<never, never> & Record<"bump", (props: undefined, ctx: ActionContext<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>>) => void>>
action
("bump", (_: undefined_, { state: IslandState<MergeState<Record<never, never>, "n", number>>state }) => {
state: IslandState<MergeState<Record<never, never>, "n", number>>state.
n: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
n
((n: numbern) => n: numbern + 1);
}) .
IlhaBuilder<{ label: string; }, MergeState<Record<never, never>, "n", number>, Record<never, never>, Record<never, never> & Record<"bump", (props: undefined, ctx: ActionContext<...>) => void>>.render(fn: (ctx: RenderContext<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>, Record<never, never> & Record<"bump", (props: undefined, ctx: ActionContext<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>>) => void>>) => string | RawHtml): Island<{
    label: string;
}, MergeState<Record<never, never>, "n", number>>
render
(({
input: {
    label: string;
}
input
, state: IslandState<MergeState<Record<never, never>, "n", number>>state,
action: IslandActions<Record<never, never> & Record<"bump", (props: undefined, ctx: ActionContext<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>>) => void>>
action
}) => (
<> {
input: {
    label: string;
}
input
.label: stringlabel}:{state: IslandState<MergeState<Record<never, never>, "n", number>>state.
n: MarkedSignalAccessor
() => number (+1 overload)
n
()}
<
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
type?: RawHtml | "button" | "reset" | "submit" | undefinedtype="button"
onclick?: NativeEventHandler<PointerEvent & {
    readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick
={
action: IslandActions<Record<never, never> & Record<"bump", (props: undefined, ctx: ActionContext<{
    label: string;
}, MergeState<Record<never, never>, "n", number>, Record<never, never>>) => void>>
action
.bump: ActionAccessor<undefined, void>bump}>
+ </
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
</> )); const const List: Island<RootInput, MergeState<RootState, "order", string[]>>List =
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<string[], "order">(key: "order", init?: StateInit<RootInput, string[]> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "order", string[]>, RootDerived, RootActions>state("order", ["a", "b"] as string[]) .IlhaBuilder<RootInput, MergeState<RootState, "order", string[]>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "order", string[]>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "order", string[]>>render(({ state: IslandState<MergeState<RootState, "order", string[]>>state }) => ( <"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> {state: IslandState<MergeState<RootState, "order", string[]>>state.
order: MarkedSignalAccessor
() => string[] (+1 overload)
order
().Array<string>.map<JSX.Element>(callbackfn: (value: string, index: number, array: string[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((k: stringk) => {
const
const Keyed: KeyedIsland<{
    label: string;
}>
Keyed
=
const Item: Island<{
    label: string;
}, MergeState<Record<never, never>, "n", number>>
Item
.
Island<{ label: string; }, MergeState<Record<never, never>, "n", number>>.key(key: string): KeyedIsland<{
    label: string;
}>
key
(k: stringk);
return <
const Keyed: KeyedIsland<{
    label: string;
}>
Keyed
label?: string | undefinedlabel={k: stringk} />;
})} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> ));

The tag must be a non-empty name matching ilha’s rule: start with an ASCII letter, then letters, digits, or hyphens (same idea as built-in tags like span, li, section — not arbitrary strings). Invalid names throw at builder time.

.as() on the ilha builder is only for child island slot wrappers. List helpers such as each() (for example from Quando) use a separate .as((item, index) => …) API for mapping items to JSX — not the same method.

Function components

Small JSX helper components can return JSX or strings. They receive an object even when no props are passed, so destructuring is safe:

function 
function EmptyState({ label, }: {
    label?: string;
}): JSX.Element
EmptyState
({
label: stringlabel = "Nothing here", }: { label?: string | undefinedlabel?: string; }) { return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{label: stringlabel}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>; } const const out: JSX.Elementout = <
function EmptyState({ label, }: {
    label?: string;
}): JSX.Element
EmptyState
/>;

A plain function component can use lowercase event props when an island renders it. The handler belongs to the containing island and shares its re-render and unmount lifecycle:

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";
function
function ChangeButton({ change }: {
    change: () => void;
}): JSX.Element
ChangeButton
({ change: () => voidchange }: { change: () => voidchange: () => void }) {
return <
"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
={change: () => voidchange}>Change</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>;
} const const Parent: Island<RootInput, MergeState<RootState, "value", string>>Parent =
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<string, "value">(key: "value", init?: StateInit<RootInput, string> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "value", string>, RootDerived, RootActions>state("value", "bar") .IlhaBuilder<RootInput, MergeState<RootState, "value", string>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "value", string>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "value", string>>render(({ state: IslandState<MergeState<RootState, "value", string>>state }) => ( <"section": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>section> <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{state: IslandState<MergeState<RootState, "value", string>>state.
value: MarkedSignalAccessor
() => string (+1 overload)
value
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>
<
function ChangeButton({ change }: {
    change: () => void;
}): JSX.Element
ChangeButton
change: () => voidchange={() => state: IslandState<MergeState<RootState, "value", string>>state.
value: MarkedSignalAccessor
(value: SignalSetter<string>) => void (+1 overload)
value
("baz")} />
</"section": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>section> ));

Rendering a plain component outside an island produces static HTML, so event props stay inert. For independently mountable UI, promote it with ilha(() => JSX).

Template bindings

Inside JSX, use bind:property={signal} to create two-way bindings between form elements and signals. When the signal changes, the element updates. When the user interacts with the element, the signal updates.

Use bind:* for synchronization and lowercase events for custom logic. You can put both on the same element, such as <input bind:value={state.name} onchange={validate} />.

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 Name: Island<RootInput, MergeState<RootState, "name", string>>Name =
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<string, "name">(key: "name", init?: StateInit<RootInput, string> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "name", string>, RootDerived, RootActions>state("name", "Ada").IlhaBuilder<RootInput, MergeState<RootState, "name", string>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "name", string>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "name", string>>render(({ state: IslandState<MergeState<RootState, "name", string>>state }) => (
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
"bind:value"?: BindAccessor<string | number> | undefinedbind:"bind:value"?: BindAccessor<string | number> | undefinedvalue={state: IslandState<MergeState<RootState, "name", string>>state.name: SignalAccessor<string>name} />
<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Hello, {state: IslandState<MergeState<RootState, "name", string>>state.
name: MarkedSignalAccessor
() => string (+1 overload)
name
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>
</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div> ));

Supported bindings

Binding Element Bound property Trigger event
bind:value <input>, <textarea>, <select> value input
bind:valueAsNumber <input type="number"> valueAsNumber input
bind:valueAsDate <input type="date"> valueAsDate input
bind:checked <input type="checkbox"> checked change
bind:group <input type="radio">, <input type="checkbox"> checked / value change
bind:open <details> open toggle
bind:files <input type="file"> files change
bind:this Any element element reference

The element type is detected at runtime — no configuration needed.

Number coercion warnings

bind:value and radio-group bind:group bindings coerce the DOM’s string value back to a number when the bound signal currently holds a number. If the DOM value cannot be parsed as a number (an empty or non-numeric input), ilha coerces to 0 and logs a dev warning suggesting bind:valueAsNumber instead — which yields null on invalid input rather than silently coercing to 0. Checkbox-array bind:group bindings are exempt: an option value that fails to parse is kept as its raw string, with no 0 fallback:

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";
// Prefer this for numeric inputs: const const Age: Island<RootInput, MergeState<RootState, "age", number>>Age =
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, "age">(key: "age", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "age", number>, RootDerived, RootActions>state("age", 0) .IlhaBuilder<RootInput, MergeState<RootState, "age", number>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "age", number>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "age", number>>render(({ state: IslandState<MergeState<RootState, "age", number>>state }) => ( <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | undefinedtype="number" "bind:valueAsNumber"?: BindAccessor<number | null> | undefinedbind:"bind:valueAsNumber"?: BindAccessor<number | null> | undefinedvalueAsNumber={state: IslandState<MergeState<RootState, "age", number>>state.age: SignalAccessor<number>age} />
));

The warning is suppressed in production.

Areia in nested child islands: use bind:checked={state.flag} on &lt;Switch /&gt; and bind:group={state.tab} on &lt;Tabs /> (recent areia mounts Switch.Root / Tabs.Root when bind props are present). Controlled checked + onCheckedChange or value + onValueChange still work.

Radio and checkbox groups

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 Plan: Island<RootInput, MergeState<RootState, "plan", string>>Plan =
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<string, "plan">(key: "plan", init?: StateInit<RootInput, string> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "plan", string>, RootDerived, RootActions>state("plan", "pro").IlhaBuilder<RootInput, MergeState<RootState, "plan", string>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "plan", string>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "plan", string>>render(({ state: IslandState<MergeState<RootState, "plan", string>>state }) => (
<> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "radio" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "range" | "tel" | "url" | "week" | undefinedtype="radio" name?: string | RawHtml | undefinedname="plan" value?: string | number | RawHtml | undefinedvalue="free" "bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedbind:"bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedgroup={state: IslandState<MergeState<RootState, "plan", string>>state.plan: SignalAccessor<string>plan} /> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "radio" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "range" | "tel" | "url" | "week" | undefinedtype="radio" name?: string | RawHtml | undefinedname="plan" value?: string | number | RawHtml | undefinedvalue="pro" "bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedbind:"bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedgroup={state: IslandState<MergeState<RootState, "plan", string>>state.plan: SignalAccessor<string>plan} /> </> )); const const Tags: Island<RootInput, MergeState<RootState, string, string[]>>Tags =
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<string[], string>(key: string, init?: StateInit<RootInput, string[]> | undefined): IlhaBuilder<RootInput, MergeState<RootState, string, string[]>, RootDerived, RootActions>state<string[]>("tags", ["ts"]) .IlhaBuilder<RootInput, MergeState<RootState, string, string[]>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, string, string[]>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, string, string[]>>render(({ state: IslandState<MergeState<RootState, string, string[]>>state }) => ( <> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "radio" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "range" | "tel" | "url" | "week" | undefinedtype="checkbox" name?: string | RawHtml | undefinedname="tag" value?: string | number | RawHtml | undefinedvalue="js" "bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedbind:"bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedgroup={state: IslandState<MergeState<RootState, string, string[]>>state.SignalAccessor<string[]>tags} /> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "radio" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "range" | "tel" | "url" | "week" | undefinedtype="checkbox" name?: string | RawHtml | undefinedname="tag" value?: string | number | RawHtml | undefinedvalue="ts" "bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedbind:"bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedgroup={state: IslandState<MergeState<RootState, string, string[]>>state.SignalAccessor<string[]>tags} /> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "radio" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "range" | "tel" | "url" | "week" | undefinedtype="checkbox" name?: string | RawHtml | undefinedname="tag" value?: string | number | RawHtml | undefinedvalue="rust" "bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedbind:"bind:group"?: BindAccessor<string | number | (string | number)[]> | undefinedgroup={state: IslandState<MergeState<RootState, string, string[]>>state.SignalAccessor<string[]>tags} /> </> ));

Nested fields with .select()

When state holds an object or array, bind to a nested slice with .select() instead of replacing the whole value on every keystroke:

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 Profile: Island<RootInput, MergeState<RootState, "user", {
    name: string;
    role: string;
}>>
Profile
=
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<{
    name: string;
    role: string;
}, "user">(key: "user", init?: StateInit<RootInput, {
    name: string;
    role: string;
}> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "user", {
    name: string;
    role: string;
}>, RootDerived, RootActions>
state
("user", { name: stringname: "Ada", role: stringrole: "dev" })
.
IlhaBuilder<RootInput, MergeState<RootState, "user", { name: string; role: string; }>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "user", {
    name: string;
    role: string;
}>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "user", {
    name: string;
    role: string;
}>>
render
(({
state: IslandState<MergeState<RootState, "user", {
    name: string;
    role: string;
}>>
state
}) => (
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div> <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
"bind:value"?: BindAccessor<string | number> | undefinedbind:"bind:value"?: BindAccessor<string | number> | undefinedvalue={
state: IslandState<MergeState<RootState, "user", {
    name: string;
    role: string;
}>>
state
.
user: SignalAccessor<{
    name: string;
    role: string;
}>
user
.
MarkedSignalAccessor<{ name: string; role: string; }>.select<string>(selector: (state: {
    name: string;
    role: string;
}) => string): MarkedSignalAccessor<string>
select
((
u: {
    name: string;
    role: string;
}
u
) =>
u: {
    name: string;
    role: string;
}
u
.name: stringname)} />
<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{
state: IslandState<MergeState<RootState, "user", {
    name: string;
    role: string;
}>>
state
.
user: MarkedSignalAccessor
() => {
    name: string;
    role: string;
} (+1 overload)
user
().name: stringname}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>
</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div> ));

In a list, select the item field you need inside .map():

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 Todos: Island<RootInput, MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>>
Todos
=
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<{
    text: string;
    completed: boolean;
}[], "todos">(key: "todos", init?: StateInit<RootInput, {
    text: string;
    completed: boolean;
}[]> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>, RootDerived, RootActions>
state
("todos", [{ text: stringtext: "Learn ilha", completed: booleancompleted: false }])
.
IlhaBuilder<RootInput, MergeState<RootState, "todos", { text: string; completed: boolean; }[]>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>>
render
(({
state: IslandState<MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>>
state
}) => (
<"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> {
state: IslandState<MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>>
state
.
todos: MarkedSignalAccessor
() => {
    text: string;
    completed: boolean;
}[] (+1 overload)
todos
().
Array<{ text: string; completed: boolean; }>.map<JSX.Element>(callbackfn: (value: {
    text: string;
    completed: boolean;
}, index: number, array: {
    text: string;
    completed: boolean;
}[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((
todo: {
    text: string;
    completed: boolean;
}
todo
, index: numberindex) => (
<
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
JSX.IntrinsicAttributes.key?: ((string | number) & (string | number | RawHtml)) | undefinedkey={
todo: {
    text: string;
    completed: boolean;
}
todo
.text: stringtext}>
<
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | undefinedtype="checkbox" "bind:checked"?: BindAccessor<boolean> | undefinedbind:"bind:checked"?: BindAccessor<boolean> | undefinedchecked={
state: IslandState<MergeState<RootState, "todos", {
    text: string;
    completed: boolean;
}[]>>
state
.
todos: SignalAccessor<{
    text: string;
    completed: boolean;
}[]>
todos
.
MarkedSignalAccessor<{ text: string; completed: boolean; }[]>.select<boolean>(selector: (state: {
    text: string;
    completed: boolean;
}[]) => boolean): MarkedSignalAccessor<boolean>
select
(
(
t: {
    text: string;
    completed: boolean;
}[]
t
) =>
t: {
    text: string;
    completed: boolean;
}[]
t
[index: numberindex].completed: booleancompleted,
)} /> {
todo: {
    text: string;
    completed: boolean;
}
todo
.text: stringtext}
</
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>
))} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul> ));

The selector must traverse nested state (for example (u) => u.name or (t) => t[i].completed). Writes update only that path — siblings and unrelated fields stay intact.

Element references

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 Focus: Island<RootInput, MergeState<RootState, "ref", HTMLInputElement | null>>Focus =
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<HTMLInputElement | null, "ref">(key: "ref", init?: StateInit<RootInput, HTMLInputElement | null> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "ref", HTMLInputElement | null>, RootDerived, RootActions>state("ref", null as HTMLInputElement | null) .IlhaBuilder<RootInput, MergeState<RootState, "ref", HTMLInputElement | null>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "ref", HTMLInputElement | null>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "ref", HTMLInputElement | null>>render(({ state: IslandState<MergeState<RootState, "ref", HTMLInputElement | null>>state }) => <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
"bind:this"?: ElementRefAccessor<HTMLInputElement> | undefinedbind:"bind:this"?: ElementRefAccessor<HTMLInputElement> | undefinedthis={state: IslandState<MergeState<RootState, "ref", HTMLInputElement | null>>state.ref: SignalAccessor<HTMLInputElement | null>ref} />);

External signals

Any signal created with signal() works as a binding target, including nested slices via .select():

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
, { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
} from "ilha";
const const username: SignalAccessor<string>username = signal<string>(initial: string): SignalAccessor<string>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
("");
const const LoginForm: Island<RootInput, RootState>LoginForm = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
"bind:value"?: BindAccessor<string | number> | undefinedbind:"bind:value"?: BindAccessor<string | number> | undefinedvalue={const username: SignalAccessor<string>username} placeholder?: string | RawHtml | undefinedplaceholder="Username" />
)); const
const profile: SignalAccessor<{
    user: {
        name: string;
    };
}>
profile
=
signal<{
    user: {
        name: string;
    };
}>(initial: {
    user: {
        name: string;
    };
}): SignalAccessor<{
    user: {
        name: string;
    };
}>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
({
user: {
    name: string;
}
user
: { name: stringname: "Ada" } });
const const Settings: Island<RootInput, RootState>Settings = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => ( <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
"bind:value"?: BindAccessor<string | number> | undefinedbind:"bind:value"?: BindAccessor<string | number> | undefinedvalue={
const profile: SignalAccessor<{
    user: {
        name: string;
    };
}>
profile
.
MarkedSignalAccessor<{ user: { name: string; }; }>.select<string>(selector: (state: {
    user: {
        name: string;
    };
}) => string): MarkedSignalAccessor<string>
select
((
p: {
    user: {
        name: string;
    };
}
p
) =>
p: {
    user: {
        name: string;
    };
}
p
.
user: {
    name: string;
}
user
.name: stringname)}
placeholder?: string | RawHtml | undefinedplaceholder="Name" /> ));

Async rendering

If the island uses async .derived() values, await the island to resolve all of them before rendering:

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 Island: Island<RootInput, RootState>Island =
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>.derived<"user", any>(key: "user", fn: DerivedFn<RootInput, RootState, any>): IlhaBuilder<RootInput, RootState, RootDerived & Record<"user", any>, RootActions>derived("user", async () => { const const res: Responseres = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
("/api/user");
return const res: Responseres.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
();
}) .IlhaBuilder<RootInput, RootState, RootDerived & Record<"user", any>, RootActions>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived & Record<"user", any>, RootActions>) => string | RawHtml): Island<RootInput, RootState>render(({ derived: IslandDerived<RootDerived & Record<"user", any>>derived }) => { if (derived: IslandDerived<RootDerived & Record<"user", any>>derived.user: DerivedAccessor<any>user.loading: booleanloading) return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Loading…</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>; return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{derived: IslandDerived<RootDerived & Record<"user", any>>derived.user: () => any (+1 overload)user()?.name}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>; }); // Async — waits for derived values const const html: stringhtml = await
const Island: Island
(props?: Partial<RootInput> | undefined) => string | Promise<string>
Island
();
// Sync — derived renders in loading state const const html2: stringhtml2 = const Island: Island<RootInput, RootState>Island.Island<RootInput, RootState>.toString(props?: Partial<RootInput> | undefined): stringtoString();

What .render() returns

Calling .render() produces an Island object with these methods:

island.toString(props?) // always renders synchronously
await island(props) // waits for asynchronous derived values
island.mount(host, props?) // mounts into a DOM element, returns unmount()
island.hydratable(props, options) // renders wrapped in hydration container
island.key(key) // stable keyed invocation for reorderable lists
island.define(tagName, options?) // registers the island as a custom element

See Island.define() to use an island from plain HTML or any other framework via custom elements.

Notes

  • .render() must be called exactly once and always last in the chain.
  • JSX output follows ilha’s escaping rules; use raw() only for trusted markup.
  • Use lowercase native event props such as onclick and one optional modifier such as oninput:abortable; camelCase onClick props are omitted. Use .on() for host listeners, selectors, or combined modifiers.
  • The render function runs on every re-render triggered by a signal change. Keep it fast and free of side effects — use .effect() or .onMount() for side effects instead.
  • During SSR the render function runs synchronously. Avoid browser-only APIs (window, document) at the top level of the render function.
  • The render function does not receive host — if you need the host element, use .onMount() or .effect().
  • An unbound <textarea> no longer loses in-progress user typing on unrelated re-renders — its value is only updated by the morph engine when the template’s text content actually changed.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close