Input
Declares the island's external props and their types. Two forms are supported: a type-only generic for when you just need TypeScript inference, and a Standard Schema-compatible validator (Zod, Valibot, ArkType, etc.) for runtime validation too.
Basic usage
Type-only — TypeScript inference, no runtime validation:
With a schema — inference plus runtime validation and coercion:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, 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;
... 4 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: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, 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;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.input<z.ZodObject<{
name: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
name: string;
}, {
name?: string | undefined;
}>>(schema: z.ZodObject<{
name: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
name: string;
}, {
name?: string | undefined;
}>): IlhaBuilder<{
name: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>> (+1 overload)
input(import zz.object<{
name: z.ZodDefault<z.ZodString>;
}>(shape: {
name: z.ZodDefault<z.ZodString>;
}, params?: z.RawCreateParams): z.ZodObject<{
name: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
name: string;
}, {
name?: string | undefined;
}>
export object
object({ name: z.ZodDefault<z.ZodString>name: import zz.function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string().ZodType<string, ZodStringDef, string>.default(def: string): z.ZodDefault<z.ZodString> (+1 overload)default("World") }))
.IlhaBuilder<{ name: string; } & Record<string, unknown>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{
name: string;
} & Record<string, unknown>, 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 }) => <IntrinsicElements[string]: anyp>Hello, {input: {
name: string;
} & Record<string, unknown>
input.name: stringname}!</IntrinsicElements[string]: anyp>);
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(), any props passed to an island are untyped and unvalidated. Adding a type or schema gives you:
- Full TypeScript inference for
input inside .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(schema)TypeScript inference ✓ ✓ Runtime validation — ✓ Default values — ✓ (via schema) Extra dependency — ✓ (Zod, Valibot, etc.)
Use .input<T>() for simple islands where you control the call sites and don't need validation. Use .input(schema) when you need defaults, coercion, or validation — especially for islands that are hydrated from serialized server props.
Using defaults
Defaults are defined in the schema, not in ilha. Any Standard Schema validator that supports defaults will apply them automatically when a prop is omitted.
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, 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;
... 4 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: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, 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;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.input<z.ZodObject<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
title: string;
accent: string;
}, {
title: string;
accent?: string | undefined;
}>>(schema: z.ZodObject<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
title: string;
accent: string;
}, {
title: string;
accent?: string | undefined;
}>): IlhaBuilder<{
title: string;
accent: string;
} & Record<string, unknown>, Record<never, never>, Record<never, never>> (+1 overload)
input(
import zz.object<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}>(shape: {
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, params?: z.RawCreateParams): z.ZodObject<{
title: z.ZodString;
accent: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
title: string;
accent: string;
}, {
title: string;
accent?: string | undefined;
}>
export object
object({
title: z.ZodStringtitle: import zz.function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string(),
accent: z.ZodDefault<z.ZodString>accent: import zz.function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string().ZodType<string, ZodStringDef, 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>>.render(fn: (ctx: RenderContext<{
title: string;
accent: string;
} & Record<string, unknown>, 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 }) => (
<IntrinsicElements[string]: anydiv style: stringstyle={`color:${input: {
title: string;
accent: string;
} & Record<string, unknown>
input.accent: stringaccent}`}>{input: {
title: string;
accent: string;
} & Record<string, unknown>
input.title: stringtitle}</IntrinsicElements[string]: anydiv>
));
State initialized from input
Once you have typed input, you can use it to initialize state:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, 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;
... 4 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: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, 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;
... 4 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.input<z.ZodObject<{
start: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
start: number;
}, {
start?: number | undefined;
}>>(schema: z.ZodObject<{
start: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
start: number;
}, {
start?: number | undefined;
}>): IlhaBuilder<{
start: number;
} & Record<string, unknown>, Record<never, never>, Record<never, never>> (+1 overload)
input(import zz.object<{
start: z.ZodDefault<z.ZodNumber>;
}>(shape: {
start: z.ZodDefault<z.ZodNumber>;
}, params?: z.RawCreateParams): z.ZodObject<{
start: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
start: number;
}, {
start?: number | undefined;
}>
export object
object({ start: z.ZodDefault<z.ZodNumber>start: import zz.function number(params?: z.RawCreateParams & {
coerce?: boolean;
}): z.ZodNumber
export number
number().ZodType<number, ZodNumberDef, number>.default(def: number): z.ZodDefault<z.ZodNumber> (+1 overload)default(0) }))
.IlhaBuilder<{ start: number; } & Record<string, unknown>, 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>>
state("count", ({ start: numberstart }) => start: numberstart)
.IlhaBuilder<{ start: number; } & Record<string, unknown>, MergeState<Record<never, never>, "count", number>, Record<never, never>>.render(fn: (ctx: RenderContext<{
start: number;
} & Record<string, unknown>, MergeState<Record<never, never>, "count", number>, 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 }) => <IntrinsicElements[string]: anyp>{state: IslandState<MergeState<Record<never, never>, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count()}</IntrinsicElements[string]: anyp>);
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 as Record<string, unknown> with no validation.