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-props on 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.