Skip to content

Define

Registers the island as a custom element — usable from plain HTML, or from any framework that can render arbitrary tags, without touching ilha's mount() API at all. This is one of the easiest ways to drop an ilha island into an existing app, a CMS template, or a static page.

Basic usage

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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
from "ilha";
const
const Badge: Island<{ label: string; }, Record<never, never>>
Badge
=
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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
.
IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.input<{ label: string; }>(defaults: { label: string; }): IlhaBuilder<{ label: string; }, Record<never, never>, Record<never, never>> (+2 overloads)
input
<{
label: string
label
: string }>({
label: string
label
: "?" })
.
IlhaBuilder<{ label: string; }, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{ label: string; }, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{ label: string; }, Record<never, never>>
render
(({
input: { label: string; }
input
}) => <
IntrinsicElements[string]: any
span
>{
input: { label: string; }
input
.
label: string
label
}</
IntrinsicElements[string]: any
span
>);
const Badge: Island<{ label: string; }, Record<never, never>>
Badge
.
Island<{ label: string; }, Record<never, never>>.define(tagName: string, options?: { observe?: string[]; }): void
Register this island as a custom element, usable from plain HTML or any framework: `Counter.define("x-counter", { observe: ["label"] })` then `<x-counter label="hi"></x-counter>`. Observed attributes become string input props and re-resolve input on change; richer props can be assigned via the element's `props` property. Mounts on connect, unmounts on disconnect. No-op (with a dev warning) where customElements is missing.
define
("x-ilha-badge", {
observe?: string[] | undefined
observe
: ["label"] });
<x-ilha-badge label="hello"></x-ilha-badge>

The element mounts the island as soon as it connects to the DOM, and unmounts it when it disconnects — no explicit mount() call needed anywhere.

Options

interface DefineOptions {
  observe?: string[]; // attribute names to watch and pass through as string props
}

Observed attributes

Attributes named in observe become string input props. Changing the attribute re-resolves input and re-renders the island automatically:

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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
from "ilha";
const
const Counter: Island<{ start: string; }, 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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
.
IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.input<{ start: string; }>(defaults: { start: string; }): IlhaBuilder<{ start: string; }, Record<never, never>, Record<never, never>> (+2 overloads)
input
<{
start: string
start
: string }>({
start: string
start
: "0" })
.
IlhaBuilder<{ start: string; }, Record<never, never>, Record<never, never>>.state<number, "count">(key: "count", init?: StateInit<{ start: string; }, number> | undefined): IlhaBuilder<{ start: string; }, MergeState<Record<never, never>, "count", number>, Record<never, never>>
state
("count", ({
start: string
start
}) =>
var Number: NumberConstructor (value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number
(
start: string
start
))
.
IlhaBuilder<{ start: string; }, MergeState<Record<never, never>, "count", number>, Record<never, never>>.render(fn: (ctx: RenderContext<{ start: string; }, MergeState<Record<never, never>, "count", number>, Record<never, never>>) => string | RawHtml): Island<{ start: string; }, MergeState<Record<never, never>, "count", number>>
render
(({
state: IslandState<MergeState<Record<never, never>, "count", number>>
state
}) => <
IntrinsicElements[string]: any
p
>{
state: IslandState<MergeState<Record<never, never>, "count", number>>
state
.
count: MarkedSignalAccessor () => number (+1 overload)
count
()}</
IntrinsicElements[string]: any
p
>);
const Counter: Island<{ start: string; }, MergeState<Record<never, never>, "count", number>>
Counter
.
Island<{ start: string; }, MergeState<Record<never, never>, "count", number>>.define(tagName: string, options?: { observe?: string[]; }): void
Register this island as a custom element, usable from plain HTML or any framework: `Counter.define("x-counter", { observe: ["label"] })` then `<x-counter label="hi"></x-counter>`. Observed attributes become string input props and re-resolve input on change; richer props can be assigned via the element's `props` property. Mounts on connect, unmounts on disconnect. No-op (with a dev warning) where customElements is missing.
define
("x-counter", {
observe?: string[] | undefined
observe
: ["start"] });
<x-counter start="10"></x-counter>

<script>
  document
    .querySelector("x-counter")
    .setAttribute("start", "20");
</script>

Because HTML attributes are always strings, coerce them to the type you need (as Number(start) does above) inside .state() or .derived().

Rich props via the props property

Attributes can only carry strings. For objects, arrays, or other non-string props, assign them directly to the element's props property — set from any JavaScript, including another framework's component:

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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
from "ilha";
const
const List: Island<{ items: string[]; }, Record<never, never>>
List
=
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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
.
IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.input<{ items: string[]; }>(defaults: { items: string[]; }): IlhaBuilder<{ items: string[]; }, Record<never, never>, Record<never, never>> (+2 overloads)
input
<{
items: string[]
items
: string[] }>({
items: string[]
items
: [] })
.
IlhaBuilder<{ items: string[]; }, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<{ items: string[]; }, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<{ items: string[]; }, Record<never, never>>
render
(({
input: { items: string[]; }
input
}) => (
<
IntrinsicElements[string]: any
ul
>
{
input: { items: string[]; }
input
.
items: string[]
items
.
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
((
i: string
i
) => (
<
IntrinsicElements[string]: any
li
>{
i: string
i
}</
IntrinsicElements[string]: any
li
>
))} </
IntrinsicElements[string]: any
ul
>
));
const List: Island<{ items: string[]; }, Record<never, never>>
List
.
Island<{ items: string[]; }, Record<never, never>>.define(tagName: string, options?: { observe?: string[]; }): void
Register this island as a custom element, usable from plain HTML or any framework: `Counter.define("x-counter", { observe: ["label"] })` then `<x-counter label="hi"></x-counter>`. Observed attributes become string input props and re-resolve input on change; richer props can be assigned via the element's `props` property. Mounts on connect, unmounts on disconnect. No-op (with a dev warning) where customElements is missing.
define
("x-ilha-list");
const host = document.createElement(
  "x-ilha-list",
) as HTMLElement & {
  props?: { items: string[] };
};
host.props = { items: ["a", "b"] };
document.body.appendChild(host);

// Later updates re-render the island:
host.props = { items: ["a", "b", "c"] };

props is merged over attribute-derived props, so you can combine both — observed attributes for simple values, props for anything richer.

Falling back to data-ilha-props

If neither observe attributes nor props are set, the element falls back to reading data-ilha-props off itself — the same serialized-props convention used by .hydratable():

<x-ilha-badge
  data-ilha-props='{"label":"hello"}'
></x-ilha-badge>

Using from another framework

Because the custom element mounts and unmounts itself via the standard connectedCallback / disconnectedCallback lifecycle, it works anywhere custom elements are supported — React, Vue, Svelte, plain HTML, or a CMS template — with no ilha-specific glue code required in the host framework.

// Inside a React component, for example:
function Page() {
  return <x-ilha-badge label="from React" />;
}

SSR safety

.define() is a no-op with a dev warning where customElements is unavailable (for example, during SSR in a Node environment):

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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
from "ilha";
const
const Badge: Island<Record<string, unknown>, Record<never, never>>
Badge
=
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; ... 5 more ...; onUncaughtError: typeof onUncaughtError; }
ilha
.
IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<never, never>, Record<never, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<never, never>>
render
(() => <
IntrinsicElements[string]: any
span
>hi</
IntrinsicElements[string]: any
span
>);
// Safe to call during module init even on the server — // logs a dev warning and returns without registering anything.
const Badge: Island<Record<string, unknown>, Record<never, never>>
Badge
.
Island<Record<string, unknown>, Record<never, never>>.define(tagName: string, options?: { observe?: string[]; }): void
Register this island as a custom element, usable from plain HTML or any framework: `Counter.define("x-counter", { observe: ["label"] })` then `<x-counter label="hi"></x-counter>`. Observed attributes become string input props and re-resolve input on change; richer props can be assigned via the element's `props` property. Mounts on connect, unmounts on disconnect. No-op (with a dev warning) where customElements is missing.
define
("x-ilha-badge");

Notes

  • Registering the same tag name twice logs a dev warning and skips the second registration — customElements.define() throws if called twice for the same tag, so .define() guards against that.
  • .define() can be called anywhere after .render() finalizes the builder chain — typically once at module scope, next to the island's definition.
  • Combine .define() with .hydratable() if you also render the island server-side elsewhere — they are independent integration paths for the same island.