#Composing Islands
Child islands are interpolated directly inside a parent's html`` template. The child is rendered inline during SSR and activated independently on the client. Each child is managed as its own island — it owns its own state, lifecycle, and reactivity.
#Basic usage
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
const const Icon: Island<Record<string, unknown>, Record<string, never>> Icon = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (() => `<svg>…</svg>`);
const const Card: Island<Record<string, unknown>, Record<string, never>> Card = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (
() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `
<div class="card">
${const Icon: Island<Record<string, unknown>, Record<string, never>> Icon }
<p>Card content</p>
</div>
`,
);#Passing props to a child
Call the child island with a props object to forward data:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
import { import z z } from "zod";
const const Badge: Island<{
label: string;
color: string;
} & Record<string, unknown>, Record<string, never>>
Badge = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.input<z.ZodObject<{
label: z.ZodString;
color: z.ZodDefault<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
label: z.ZodString;
color: z.ZodDefault<z.ZodString>;
}, z.core.$strip>): IlhaBuilder<{
label: string;
color: string;
} & Record<string, unknown>, Record<string, never>, Record<string, never>> (+1 overload)
input (
import z z .function object<{
label: z.ZodString;
color: z.ZodDefault<z.ZodString>;
}>(shape?: {
label: z.ZodString;
color: z.ZodDefault<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;
color: z.ZodDefault<z.ZodString>;
}, z.core.$strip>
object ({
label: z.ZodString label : import z z .function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload) string (),
color: z.ZodDefault<z.ZodString> color : import z z .function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload) string ().ZodType<any, any, $ZodStringInternals<string>>.default(def: string): z.ZodDefault<z.ZodString> (+1 overload) default ("teal"),
}),
)
.IlhaBuilder<{ label: string; color: string; } & Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<{
label: string;
color: string;
} & Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<{
label: string;
color: string;
} & Record<string, unknown>, Record<string, never>>
render (({ input: {
label: string;
color: string;
} & Record<string, unknown>
input }) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html ` <span style="background:${input: {
label: string;
color: string;
} & Record<string, unknown>
input .color: string color }">${input: {
label: string;
color: string;
} & Record<string, unknown>
input .label: string label }</span> `);
const const Card: Island<Record<string, unknown>, Record<string, never>> Card = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (
() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `
<div>
${const Badge: Island
(props?: Partial<{
label: string;
color: string;
} & Record<string, unknown>> | undefined) => string | Promise<string>
Badge ({ label?: string | undefined label : "New", color?: string | undefined color : "coral" })}
<p>Content</p>
</div>
`,
);Props are validated against the child island's schema, so type errors surface at authoring time.
#Multiple children
Interpolate as many child islands as needed:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
const const Avatar: Island<Record<string, unknown>, Record<string, never>> Avatar = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (() => `<img src="/avatar.png" />`);
const const Actions: Island<Record<string, unknown>, Record<string, never>> Actions = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<button>Follow</button>`);
const const Profile: Island<Record<string, unknown>, Record<string, never>> Profile = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (
() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `
<div class="profile">
${const Avatar: Island<Record<string, unknown>, Record<string, never>> Avatar }
<div class="profile-actions">${const Actions: Island<Record<string, unknown>, Record<string, never>> Actions }</div>
</div>
`,
);#Keyed children
Use .key() when a child may reorder or appear conditionally. Keys must be unique within a parent render:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
const const Item: Island<{
name: string;
}, Record<string, never>>
Item = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.input<{
name: string;
}>(): IlhaBuilder<{
name: string;
}, Record<string, never>, Record<string, never>> (+1 overload)
input <{ name: string name : string }>().IlhaBuilder<{ name: string; }, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<{
name: string;
}, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<{
name: string;
}, Record<string, never>>
render (({ input: {
name: string;
}
input }) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<span>${input: {
name: string;
}
input .name: string name }</span>`);
const const List: Island<Record<string, unknown>, Record<string, never>> List = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (
() =>
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<ul>
${const items: any[] items .Array<any>.map<RawHtml>(callbackfn: (value: any, index: number, array: any[]) => RawHtml, thisArg?: any): RawHtml[]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: any item ) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<li>${const Item: Island<{
name: string;
}, Record<string, never>>
Item .Island<{ name: string; }, Record<string, never>>.key(key: string): KeyedIsland<{
name: string;
}>
key (item: any item .id)({ name?: string | undefined name : item: any item .name })}</li>`)}
</ul>`,
);#SSR behavior
During SSR, interpolating a child island renders its HTML inline as part of the parent's output. The child island's styles, derived values, and render function all run as part of the parent's SSR pass.
<!-- Output of Card.toString() -->
<div class="card">
<svg>…</svg>
<p>Card content</p>
</div>#Client behavior
On the client, each child is mounted independently into its own host element. The parent manages the lifecycle of its children — when the parent unmounts, all children are unmounted too.
Children are preserved across parent re-renders. If a keyed list reorders, live child subtrees are detached before the parent morphs and reattached afterwards, so DOM state, listeners, and internal state remain intact.
#Accessing child state from the parent
Child islands are self-contained — the parent cannot directly read or write the child's state. If you need to share values between parent and child, use context() to create a shared global signal that both islands can read and write:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html , const context: <T>(key: string, initial: T) => ContextSignal<T> context } from "ilha";
const const expanded: ContextSignal<boolean> expanded = context<boolean>(key: string, initial: boolean): ContextSignal<boolean> context ("card.expanded", false);
const const Toggle: Island<Record<string, unknown>, Record<string, never>> Toggle = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.on<"button@click">(selectorOrCombined: "button@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, Record<string, never>, "click", Record<string, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> (+1 overload) on ("button@click", () => const expanded: (value: boolean) => void (+1 overload) expanded (!const expanded: () => boolean (+1 overload) expanded ()))
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<button>Toggle</button>`);
const const Content: Island<Record<string, unknown>, Record<string, never>> Content = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, Record<string, never>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> effect (() => {
// reacts to expanded signal from sibling
})
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<p>Content</p>`);
const const Card: Island<Record<string, unknown>, Record<string, never>> Card = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha .IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>> render (() => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html ` <div>${const Toggle: Island<Record<string, unknown>, Record<string, never>> Toggle } ${const Content: Island<Record<string, unknown>, Record<string, never>> Content }</div> `);#Notes
- The parent's render cycle and the child's render cycle are independent. A state change in a child does not trigger a re-render in the parent.
- Child props are serialized as
data-ilha-propson the child's host element, so they are available during hydration without passing them again manually. - A dev warning is logged when two children in the same parent render share the same key.