Declare the island’s external props and their types. Use a type-only generic for TypeScript inference or a Standard Schema-compatible validator for runtime validation and coercion.
Use ilha<Props>(({ input }) => JSX) when typed props are the only capability you need. Expand to .input<Props>().render(...) when you add defaults, runtime validation, or other builder capabilities.
Basic usage
Type-only — TypeScript inference, no runtime validation:
Default props object — inference plus runtime defaults (shallow merge, no validator):
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Greeting: Island<{
name: string;
}, Record<never, never>>
Greeting = 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<{
name: string;
}>(defaults: {
name: string;
}): IlhaBuilder<{
name: string;
}, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input({ name: stringname: "World" })
.IlhaBuilder<{ name: string; }, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
name: string;
}, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
name: string;
}, Record<never, never>>
render(({ input: {
name: string;
}
input }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Hello, {input: {
name: string;
}
input.name: stringname}!</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);
const Greeting: Island<{
name: string;
}, Record<never, never>>
Greeting.Island<{ name: string; }, Record<never, never>>.toString(props?: Partial<{
name: string;
}> | undefined): string
toString(); // → <p>Hello, World!</p>
const Greeting: Island<{
name: string;
}, Record<never, never>>
Greeting.Island<{ name: string; }, Record<never, never>>.toString(props?: Partial<{
name: string;
}> | undefined): string
toString({ name?: string | undefinedname: "ilha" }); // → <p>Hello, ilha!</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 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<{
name: string;
}>(defaults: {
name: string;
}): IlhaBuilder<{
name: string;
}, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input<{ name: stringname: string }>({ name: stringname: "World" })
.IlhaBuilder<{ name: string; }, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
name: string;
}, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
name: string;
}, Record<never, never>>
render(({ input: {
name: string;
}
input }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{input: {
name: string;
}
input.name: stringname}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);With a schema — inference plus runtime validation and coercion:
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 Greeting: Island<{
name: string;
} & Record<string, unknown>, Record<never, never>>
Greeting = 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<{
name: z.ZodDefault<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
name: z.ZodDefault<z.ZodString>;
}, z.core.$strip>): IlhaBuilder<{
name: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input(import zz.function object<{
name: z.ZodDefault<z.ZodString>;
}>(shape?: {
name: 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<{
name: z.ZodDefault<z.ZodString>;
}, z.core.$strip>
object({ name: z.ZodDefault<z.ZodString>name: import zz.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("World") }))
.IlhaBuilder<{ name: string; } & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
name: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
name: string;
} & Record<string, unknown>, Record<never, never>>
render(({ input: {
name: string;
} & Record<string, unknown>
input }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Hello, {input: {
name: string;
} & Record<string, unknown>
input.name: stringname}!</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);
const Greeting: Island<{
name: string;
} & Record<string, unknown>, Record<never, never>>
Greeting.Island<{ name: string; } & Record<string, unknown>, Record<never, never>>.toString(props?: Partial<{
name: string;
} & Record<string, unknown>> | undefined): string
toString({ name?: string | undefinedname: "ilha" }); // → <p>Hello, ilha!</p>
const Greeting: Island<{
name: string;
} & Record<string, unknown>, Record<never, never>>
Greeting.Island<{ name: string; } & Record<string, unknown>, Record<never, never>>.toString(props?: Partial<{
name: string;
} & Record<string, unknown>> | undefined): string
toString(); // → <p>Hello, World!</p>Why use .input()
Without .input<T>() or the ilha<T>() shorthand, props are untyped and unvalidated. Adding a type or schema gives you:
- Full TypeScript inference for
inputinside.state(),.render(),.on(),.effect(), and every other builder method. - Runtime validation and coercion on every call, including during SSR and hydration (schema form only).
- Default values handled by the schema itself, so the island works without props (schema form only).
Choosing a form
.input<T>() |
.input({ … }) / .input<T>({ … }) |
.input(schema) |
|
|---|---|---|---|
| TypeScript inference | ✓ | ✓ | ✓ |
| Runtime validation | — | — | ✓ |
| Default values | — | ✓ (shallow merge) | ✓ (via schema) |
| Extra dependency | — | — | ✓ (Zod, Valibot, etc.) |
Use .input<T>() when callers always pass props. Use .input({ … }) for lightweight defaults without a schema (same idea as store({ … })). Use .input(schema) when you need coercion or validation — especially for islands hydrated from serialized server props.
Using defaults
POJO form — pass a defaults object to .input({ … }). Ilha shallow-merges { ...defaults, ...props } on each call.
Schema form — defaults live in the schema (Zod .default(), etc.). Validation runs on the merged props object.
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 Card: Island<{
title: string;
accent: string;
} & Record<string, unknown>, Record<never, never>>
Card = 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<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, z.core.$strip>>(schema: z.ZodObject<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, z.core.$strip>): IlhaBuilder<{
title: string;
accent: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input(
import zz.function object<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}>(shape?: {
title: z.ZodString;
accent: 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<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, z.core.$strip>
object({
title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(),
accent: z.ZodDefault<z.ZodString>accent: import zz.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<{ title: string; accent: string; } & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
title: string;
accent: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
title: string;
accent: string;
} & Record<string, unknown>, Record<never, never>>
render(({ input: {
title: string;
accent: string;
} & Record<string, unknown>
input }) => (
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div style?: string | RawHtml | StyleProps | undefinedstyle={`color:${input: {
title: string;
accent: string;
} & Record<string, unknown>
input.accent: stringaccent}`}>{input: {
title: string;
accent: string;
} & Record<string, unknown>
input.title: stringtitle}</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>
));State initialized from input
Once you have typed input, you can use it to initialize state:
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 Counter: Island<{
start: number;
} & Record<string, unknown>, MergeState<Record<never, never>, "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>.input<z.ZodObject<{
start: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>>(schema: z.ZodObject<{
start: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>): IlhaBuilder<{
start: number;
} & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>> (+2 overloads)
input(import zz.function object<{
start: z.ZodDefault<z.ZodNumber>;
}>(shape?: {
start: z.ZodDefault<z.ZodNumber>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
start: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>
object({ start: z.ZodDefault<z.ZodNumber>start: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber().ZodType<any, any, $ZodNumberInternals<number>>.default(def: number): z.ZodDefault<z.ZodNumber> (+1 overload)default(0) }))
.IlhaBuilder<{ start: number; } & Record<string, unknown>, Record<never, never>, Record<never, never>, Record<never, never>>.state<number, "count">(key: "count", init?: StateInit<{
start: number;
} & Record<string, unknown>, number> | undefined): IlhaBuilder<{
start: number;
} & Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>, Record<never, never>>
state("count", ({ start: numberstart }) => start: numberstart)
.IlhaBuilder<{ start: number; } & Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
start: number;
} & Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{
start: number;
} & Record<string, unknown>, MergeState<Record<never, never>, "count", number>>
render(({ state: IslandState<MergeState<Record<never, never>, "count", number>>state }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);The initializer function receives the resolved input object, so state stays in sync with whatever props were passed in. This works identically with both forms.
Async schemas
Async schemas are not supported. If your validator’s validate() method returns a Promise, ilha will throw at runtime. Keep schemas synchronous.
Notes
.input()must be called before any other builder method if you want the input type to flow through the chain.- Calling
.input()resets the builder — any previously chained.state()or other methods are not carried over. - If
.input()is omitted entirely, props are accepted asRecord<string, unknown>with no validation.