Skip to content

Signals

Create free-standing reactive signals and computed values, share state across islands, run top-level effects, batch writes, and peek at values without creating dependencies.

Updated View as Markdown

Reactive signals are the primitive that powers state in ilha. In addition to .state() (local to an island), ilha exports several helpers for cross-island sharing, derived values, side effects, performance, and control:

Helper Purpose
signal() Create a free-standing signal for one-off shared state
computed() Create a free-standing read-only value derived from signals
context() Create a named global signal accessible from anywhere by key
effect() Run a reactive side effect outside any island
batch() Group multiple writes into a single propagation pass
untrack() Read a signal without subscribing the surrounding scope

signal(initial)

Creates a free-standing reactive signal that lives outside any island. Useful for sharing state across multiple islands without prop drilling, or for binding form inputs to module-level state.

Basic usage

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
} from "ilha";
const const count: SignalAccessor<number>count = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const count: MarkedSignalAccessor
() => number (+1 overload)
count
(); // → 0 (read)
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count
(5); // → sets to 5 (write)
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count
((previous: numberprevious) => previous: numberprevious + 1); // → updates from the latest value

The updater form also works with context() and nested .select() accessors. If a signal stores a function, return the replacement function from an updater: callback(() => nextCallback).

Reading the signal inside any reactive scope — .render(), .derived(), .effect() — automatically subscribes that scope, so when the signal changes, dependents re-run as if it were local state.

Sharing state between islands

Because signal() returns a plain accessor, you can import it into any island. When one island writes to it, all others that read it re-render automatically:

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
, { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
} from "ilha";
const const cartCount: SignalAccessor<number>cartCount = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const const CartButton: Island<RootInput, RootState>CartButton =
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>.action<"addToCart", undefined, void>(key: "addToCart", fn: (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action("addToCart", () =>
const cartCount: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
cartCount
((count: numbercount) => count: numbercount + 1))
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action }) => ( <
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
onclick?: NativeEventHandler<PointerEvent & {
    readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick
={action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action.addToCart: ActionAccessor<undefined, void>addToCart}>Add to cart</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
)); const const CartBadge: Island<RootInput, RootState>CartBadge = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>{
const cartCount: MarkedSignalAccessor
() => number (+1 overload)
cartCount
()}</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>);

Both islands share the same cartCount signal. Clicking the button in CartButton updates the badge in CartBadge without any wiring between them.

Using signals in bind: bindings

Pass a signal directly into a bind: attribute to sync a form element with module-level 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
, { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
} from "ilha";
const const query: SignalAccessor<string>query = signal<string>(initial: string): SignalAccessor<string>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
("");
const const SearchInput: Island<RootInput, RootState>SearchInput = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha( () => <
"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
input
type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | undefinedtype="search" "bind:value"?: BindAccessor<string | number> | undefinedbind:"bind:value"?: BindAccessor<string | number> | undefinedvalue={const query: SignalAccessor<string>query} />,
); const const SearchResults: Island<RootInput, RootState>SearchResults = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Results for: {
const query: MarkedSignalAccessor
() => string (+1 overload)
query
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);

When the user types, query updates and SearchResults re-renders automatically — no wiring between islands needed.


computed(fn)

Creates a free-standing, read-only value derived from other signals. fn is lazy and cached — it re-runs only when a signal it read has changed and the computed is read again.

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function computed<T>(fn: () => T): SignalAccessor<T>
Create a free-standing read-only reactive value derived from other signals. The computation is lazy and cached: `fn` re-runs only when a signal it read changed and the computed is read again. Reading it inside a `.derived()`, `.effect()`, `.render()`, or top-level `effect()` subscribes that scope — dependents re-run when the computed's value changes. ```ts const items = ilha.signal([1, 2, 3]); const total = ilha.computed(() => items().reduce((a, b) => a + b, 0)); ```
computed
} from "ilha";
const const items: SignalAccessor<number[]>items = signal<number[]>(initial: number[]): SignalAccessor<number[]>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
([1, 2, 3]);
const const total: SignalAccessor<number>total = computed<number>(fn: () => number): SignalAccessor<number>
Create a free-standing read-only reactive value derived from other signals. The computation is lazy and cached: `fn` re-runs only when a signal it read changed and the computed is read again. Reading it inside a `.derived()`, `.effect()`, `.render()`, or top-level `effect()` subscribes that scope — dependents re-run when the computed's value changes. ```ts const items = ilha.signal([1, 2, 3]); const total = ilha.computed(() => items().reduce((a, b) => a + b, 0)); ```
computed
(() =>
const items: MarkedSignalAccessor
() => number[] (+1 overload)
items
().Array<number>.reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number (+2 overloads)
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
reduce
((a: numbera, b: numberb) => a: numbera + b: numberb, 0),
);
const total: MarkedSignalAccessor
() => number (+1 overload)
total
(); // → 6 (runs fn once, caches the result)
const total: MarkedSignalAccessor
() => number (+1 overload)
total
(); // → 6 (cached, fn does not re-run)
const items: MarkedSignalAccessor
(value: SignalSetter<number[]>) => void (+1 overload)
items
([1, 2, 3, 4]);
const total: MarkedSignalAccessor
() => number (+1 overload)
total
(); // → 10 (a dependency changed, fn re-runs)

Reading a computed inside any reactive scope — .render(), .derived(), .effect(), or top-level effect() — subscribes that scope, so dependents re-run when the computed’s resolved value changes:

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
, { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function computed<T>(fn: () => T): SignalAccessor<T>
Create a free-standing read-only reactive value derived from other signals. The computation is lazy and cached: `fn` re-runs only when a signal it read changed and the computed is read again. Reading it inside a `.derived()`, `.effect()`, `.render()`, or top-level `effect()` subscribes that scope — dependents re-run when the computed's value changes. ```ts const items = ilha.signal([1, 2, 3]); const total = ilha.computed(() => items().reduce((a, b) => a + b, 0)); ```
computed
} from "ilha";
const const base: SignalAccessor<number>base = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(1);
const const doubled: SignalAccessor<number>doubled = computed<number>(fn: () => number): SignalAccessor<number>
Create a free-standing read-only reactive value derived from other signals. The computation is lazy and cached: `fn` re-runs only when a signal it read changed and the computed is read again. Reading it inside a `.derived()`, `.effect()`, `.render()`, or top-level `effect()` subscribes that scope — dependents re-run when the computed's value changes. ```ts const items = ilha.signal([1, 2, 3]); const total = ilha.computed(() => items().reduce((a, b) => a + b, 0)); ```
computed
(() =>
const base: MarkedSignalAccessor
() => number (+1 overload)
base
() * 2);
const const Island: Island<RootInput, RootState>Island = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{
const doubled: MarkedSignalAccessor
() => number (+1 overload)
doubled
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);

Read-only

Writing to a computed is ignored and logs a dev warning — use signal() if you need a writable value:

import { function computed<T>(fn: () => T): SignalAccessor<T>
Create a free-standing read-only reactive value derived from other signals. The computation is lazy and cached: `fn` re-runs only when a signal it read changed and the computed is read again. Reading it inside a `.derived()`, `.effect()`, `.render()`, or top-level `effect()` subscribes that scope — dependents re-run when the computed's value changes. ```ts const items = ilha.signal([1, 2, 3]); const total = ilha.computed(() => items().reduce((a, b) => a + b, 0)); ```
computed
} from "ilha";
const const c: SignalAccessor<number>c = computed<number>(fn: () => number): SignalAccessor<number>
Create a free-standing read-only reactive value derived from other signals. The computation is lazy and cached: `fn` re-runs only when a signal it read changed and the computed is read again. Reading it inside a `.derived()`, `.effect()`, `.render()`, or top-level `effect()` subscribes that scope — dependents re-run when the computed's value changes. ```ts const items = ilha.signal([1, 2, 3]); const total = ilha.computed(() => items().reduce((a, b) => a + b, 0)); ```
computed
(() => 1);
(const c: SignalAccessor<number>c as (v: numberv: number) => void)(99); // ignored, dev warning logged
const c: MarkedSignalAccessor
() => number (+1 overload)
c
(); // → 1

context(key, initial)

Creates a named global signal — a reactive signal shared across all islands. Identical keys always return the same signal instance, which makes it useful for app-wide singletons (theme, locale, current user) where you want registry semantics.

import { 
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
} from "ilha";
const const theme: ContextSignal<string>theme = context<string>(key: string, initial: string): ContextSignal<string>context("app.theme", "light"); const theme: () => string (+1 overload)theme(); // → "light" const theme: (value: SignalSetter<string>) => void (+1 overload)theme("dark"); // → sets to "dark"

signal() vs context()

Both return the same accessor shape and can be used with bind: template syntax. Reach for signal() when you hold the reference yourself and import it where needed. Reach for context() when you want a name-keyed registry so the same signal can be looked up from anywhere by string key — for example, when the consumer lives in a different package or module from where the signal is defined.

Sharing state between islands

Any island that calls context() with the same key gets the same signal. When one island writes to it, all others that read it re-render automatically:

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
, {
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
} from "ilha";
const const cartCount: ContextSignal<number>cartCount = context<number>(key: string, initial: number): ContextSignal<number>context("cart.count", 0); const const CartButton: Island<RootInput, RootState>CartButton =
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>.action<"addToCart", undefined, void>(key: "addToCart", fn: (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action("addToCart", () => const cartCount: (value: SignalSetter<number>) => void (+1 overload)cartCount((count: numbercount) => count: numbercount + 1)) .IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action }) => ( <
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
onclick?: NativeEventHandler<PointerEvent & {
    readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick
={action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action.addToCart: ActionAccessor<undefined, void>addToCart}>Add to cart</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
)); const const CartBadge: Island<RootInput, RootState>CartBadge = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>{const cartCount: () => number (+1 overload)cartCount()}</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>);

Using context in bind: bindings

Pass a context signal directly into a bind: attribute to sync a form element across islands:

import ilha, { context } from "ilha";

const query = context("search.query", "");

const SearchInput = ilha(
  () => <input type="search" bind:value={query} />, 
);

const SearchResults = ilha(() => <p>Results for: {query()}</p>);

Initializing with a type

The second argument sets the initial value and infers the signal type. The type is fixed at first call — subsequent calls with the same key return the existing signal regardless of what initial value is passed:

import { 
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
} from "ilha";
const const count: ContextSignal<number>count = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 0); // creates signal<number> const const same: ContextSignal<number>same = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 999); // returns same signal, ignores 999

This means context initialization is effectively first-write-wins. Define context signals in a shared module to ensure consistent initialization across your app:

// contexts.ts
import { context } from "ilha";

export const theme = context("app.theme", "light");
export const userId = context(
  "app.userId",
  null as string | null,
);
export const sidebar = context("ui.sidebar", true);

Reading context inside effects and derived

Context signals are reactive — reading them inside .effect() or .derived() creates a dependency just like reading local 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
, {
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
} from "ilha";
const const theme: ContextSignal<string>theme = context<string>(key: string, initial: string): ContextSignal<string>context("app.theme", "light"); const const Island: Island<RootInput, RootState>Island =
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>.effect(fn: (ctx: EffectContext<RootInput, RootState, RootDerived, RootActions>) => (() => void) | void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>effect(() => { var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.documentElement: HTMLElement
The **`documentElement`** read-only property of the Document interface returns the Element that is the root element of the document (for example, the <html> element for HTML documents). [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/documentElement)
documentElement
.HTMLOrSVGElement.dataset: DOMStringMap
[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dataset)
dataset
["theme"] = const theme: () => string (+1 overload)theme();
}) .IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>render(() => <"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>content</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>);

Whenever theme is updated anywhere in the app, this effect re-runs.

SSR behavior

context() is safe to call during SSR. The registry is module-level, so signals persist for the lifetime of the process. In a server environment where requests share the same module instance, be careful not to store user-specific state in context signals — use .input() and .state() for per-request data instead.

Releasing entries — context.delete() and context.clear()

The registry is otherwise append-only. Long-lived SPAs that mint dynamic keys, HMR cycles, and tests all need a way to release entries:

import { 
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
} from "ilha";
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
.function delete(key: string): boolean
Remove a context signal from the registry. Returns true if it existed.
delete
("cart.count"); // → true if it existed, false otherwise
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
.function clear(): void
Remove all context signals from the registry (e.g. between tests).
clear
(); // remove every context signal

Deleting a key does not affect accessors already handed out — they keep working against their original signal. It only means the next context(key, initial) call creates a fresh signal instead of returning the old one.

import { 
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
} from "ilha";
const const first: ContextSignal<number>first = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 0); const first: (value: SignalSetter<number>) => void (+1 overload)first(5);
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
    delete(key: string): boolean;
    clear(): void;
}
context
.function delete(key: string): boolean
Remove a context signal from the registry. Returns true if it existed.
delete
("ui.count");
const const second: ContextSignal<number>second = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 0); // fresh signal, starts at 0 const first: () => number (+1 overload)first(); // → 5, unaffected by the delete const second: () => number (+1 overload)second(); // → 0

effect(fn)

Runs a free-standing reactive effect outside any island. fn runs once immediately and re-runs whenever a signal it read changes.

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function effect(fn: () => void | (() => void)): () => void
Run a free-standing reactive effect outside any island. `fn` runs once immediately and again whenever a signal it read changes. It may return a cleanup function, invoked before each re-run and on stop. Signal writes inside the effect are batched. Returns a stop function that disposes the effect and runs the final cleanup. ```ts const stop = effect(() => { document.title = `${cart.count()} items`; }); ```
effect
} from "ilha";
const
const cart: SignalAccessor<{
    count: number;
}>
cart
=
signal<{
    count: number;
}>(initial: {
    count: number;
}): SignalAccessor<{
    count: number;
}>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
({ count: numbercount: 0 });
const const stop: () => voidstop = function effect(fn: () => void | (() => void)): () => void
Run a free-standing reactive effect outside any island. `fn` runs once immediately and again whenever a signal it read changes. It may return a cleanup function, invoked before each re-run and on stop. Signal writes inside the effect are batched. Returns a stop function that disposes the effect and runs the final cleanup. ```ts const stop = effect(() => { document.title = `${cart.count()} items`; }); ```
effect
(() => {
var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.title: string
The **`document.title`** property gets or sets the current title of the document. When present, it defaults to the value of the <title>. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/title)
title
= `${
const cart: MarkedSignalAccessor
() => {
    count: number;
} (+1 overload)
cart
().count: numbercount} items`;
});

Import effect from "ilha" as a named exportimport { effect } from "ilha". It is deliberately not on the default export: ilha.effect() is the island builder method that registers a per-island effect. The top-level effect() documented here is a standalone helper for code that runs outside any island.

Cleanup and stopping

fn may return a cleanup function, invoked before each re-run and once more when the effect is stopped:

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function effect(fn: () => void | (() => void)): () => void
Run a free-standing reactive effect outside any island. `fn` runs once immediately and again whenever a signal it read changes. It may return a cleanup function, invoked before each re-run and on stop. Signal writes inside the effect are batched. Returns a stop function that disposes the effect and runs the final cleanup. ```ts const stop = effect(() => { document.title = `${cart.count()} items`; }); ```
effect
} from "ilha";
const const delay: SignalAccessor<number>delay = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(1000);
const const stop: () => voidstop = function effect(fn: () => void | (() => void)): () => void
Run a free-standing reactive effect outside any island. `fn` runs once immediately and again whenever a signal it read changes. It may return a cleanup function, invoked before each re-run and on stop. Signal writes inside the effect are batched. Returns a stop function that disposes the effect and runs the final cleanup. ```ts const stop = effect(() => { document.title = `${cart.count()} items`; }); ```
effect
(() => {
const const id: numberid = function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval)
setInterval
(() => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
("tick"),
const delay: MarkedSignalAccessor
() => number (+1 overload)
delay
());
return () => function clearInterval(id: number | undefined): void
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval)
clearInterval
(const id: numberid);
}); const stop: () => voidstop(); // disposes the effect and runs the final cleanup

Implicit batching

Multiple synchronous signal writes inside the effect body produce a single propagation pass, the same as inside .on() handlers or .effect():

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function effect(fn: () => void | (() => void)): () => void
Run a free-standing reactive effect outside any island. `fn` runs once immediately and again whenever a signal it read changes. It may return a cleanup function, invoked before each re-run and on stop. Signal writes inside the effect are batched. Returns a stop function that disposes the effect and runs the final cleanup. ```ts const stop = effect(() => { document.title = `${cart.count()} items`; }); ```
effect
} from "ilha";
const const a: SignalAccessor<number>a = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const const b: SignalAccessor<number>b = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
function effect(fn: () => void | (() => void)): () => void
Run a free-standing reactive effect outside any island. `fn` runs once immediately and again whenever a signal it read changes. It may return a cleanup function, invoked before each re-run and on stop. Signal writes inside the effect are batched. Returns a stop function that disposes the effect and runs the final cleanup. ```ts const stop = effect(() => { document.title = `${cart.count()} items`; }); ```
effect
(() => {
const a: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
a
(
const a: MarkedSignalAccessor
() => number (+1 overload)
a
() + 1);
const b: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
b
(
const b: MarkedSignalAccessor
() => number (+1 overload)
b
() + 1); // both writes flush together
});

batch(fn)

Runs fn as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever fn returns.

Before and after

Without batch, each write triggers its own propagation pass:

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
} from "ilha";
const const a: SignalAccessor<number>a = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const const b: SignalAccessor<number>b = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const a: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
a
(1); // → effects re-run
const b: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
b
(2); // → effects re-run again

With batch, both writes flush together:

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function batch<T>(fn: () => T): T
Run `fn` as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever `fn` returns. Note: `.on()` handlers and `.effect()` runs are batched implicitly, so you only need this when triggering multiple writes from outside an island (e.g. from a top-level event listener or async callback).
batch
} from "ilha";
const const a: SignalAccessor<number>a = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const const b: SignalAccessor<number>b = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
batch<void>(fn: () => void): void
Run `fn` as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever `fn` returns. Note: `.on()` handlers and `.effect()` runs are batched implicitly, so you only need this when triggering multiple writes from outside an island (e.g. from a top-level event listener or async callback).
batch
(() => {
const a: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
a
(10);
const b: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
b
(20);
}); // → effects re-run once

Implicit batching

.on() handlers and .effect() runs are batched implicitly, so you only need batch() when triggering multiple writes from outside an island — for example from a top-level event listener, a setTimeout callback, or a WebSocket message handler.

Nesting

Nested batch() calls are safe and only flush when the outermost batch ends:

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function batch<T>(fn: () => T): T
Run `fn` as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever `fn` returns. Note: `.on()` handlers and `.effect()` runs are batched implicitly, so you only need this when triggering multiple writes from outside an island (e.g. from a top-level event listener or async callback).
batch
} from "ilha";
const const count: SignalAccessor<number>count = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
batch<void>(fn: () => void): void
Run `fn` as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever `fn` returns. Note: `.on()` handlers and `.effect()` runs are batched implicitly, so you only need this when triggering multiple writes from outside an island (e.g. from a top-level event listener or async callback).
batch
(() => {
batch<void>(fn: () => void): void
Run `fn` as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever `fn` returns. Note: `.on()` handlers and `.effect()` runs are batched implicitly, so you only need this when triggering multiple writes from outside an island (e.g. from a top-level event listener or async callback).
batch
(() => {
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count
(1);
}); // still inside outer batch — no flush yet
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count
(2);
}); // outermost batch ends — single flush

untrack(fn)

Runs fn with reactive tracking suspended. Reading signals inside fn returns their current value without subscribing the surrounding scope. Use this in effects or deriveds when you want to peek at state without causing a re-run on its changes.

React to A, peek at B

The canonical pattern: an effect should re-run when tracked changes, but read peeked only as a one-off value:

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
, { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function untrack<T>(fn: () => T): T
Run `fn` with reactive tracking suspended. Reading signals inside `fn` returns their current value without subscribing the surrounding scope. Use this in effects/deriveds when you want to peek at state without causing a re-run on its changes.
untrack
} from "ilha";
const const tracked: SignalAccessor<number>tracked = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(0);
const const peeked: SignalAccessor<string>peeked = signal<string>(initial: string): SignalAccessor<string>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
("hello");
const const Island: Island<RootInput, RootState>Island =
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>.effect(fn: (ctx: EffectContext<RootInput, RootState, RootDerived, RootActions>) => (() => void) | void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>effect(() => { // Re-runs when `tracked` changes, but NOT when `peeked` changes. var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(
const tracked: MarkedSignalAccessor
() => number (+1 overload)
tracked
(),
untrack<string>(fn: () => string): string
Run `fn` with reactive tracking suspended. Reading signals inside `fn` returns their current value without subscribing the surrounding scope. Use this in effects/deriveds when you want to peek at state without causing a re-run on its changes.
untrack
(() =>
const peeked: MarkedSignalAccessor
() => string (+1 overload)
peeked
()),
); }) .IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>render(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>x</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);

untrack() returns whatever fn returns, so it also works for peeking at derived values or any other reactive read:

import { function signal<T>(initial: T): SignalAccessor<T>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
, function untrack<T>(fn: () => T): T
Run `fn` with reactive tracking suspended. Reading signals inside `fn` returns their current value without subscribing the surrounding scope. Use this in effects/deriveds when you want to peek at state without causing a re-run on its changes.
untrack
} from "ilha";
const const s: SignalAccessor<number>s = signal<number>(initial: number): SignalAccessor<number>
Create a free-standing reactive signal that lives outside any island. Useful for sharing state across islands without prop drilling, or for binding form inputs to module-level state via the `bind:value=${signal}` template syntax. The returned accessor is a getter when called with no arguments and a setter when called with one. Reading it inside a `.derived()`, `.effect()`, or `.render()` automatically subscribes the surrounding reactive scope — so when the signal changes, dependents re-run as if it were local state.
signal
(42);
const const value: numbervalue = untrack<number>(fn: () => number): number
Run `fn` with reactive tracking suspended. Reading signals inside `fn` returns their current value without subscribing the surrounding scope. Use this in effects/deriveds when you want to peek at state without causing a re-run on its changes.
untrack
(() =>
const s: MarkedSignalAccessor
() => number (+1 overload)
s
()); // → 42, no subscription created

Notes

  • signal() vs context() — both return the same accessor shape and can be used with bind: template syntax. Use signal() for one-off shared state where you hold the reference; use context() when you want a name-keyed registry.
  • Keys are global strings. Use namespaced keys like "app.theme" or "cart.count" to avoid accidental collisions across different parts of your app.
  • Use context.delete(key) or context.clear() to release entries — useful in HMR, tests, or long-lived SPAs that mint dynamic keys. See Releasing entries.
  • Context signals are not included in .hydratable() snapshots. If you need server-rendered context values on the client, pass them as island props via .input() and initialize the context signal inside .onMount().
  • computed() values are read-only; they cannot be used as bind: targets. Use signal() for writable bindings.
  • Top-level effect() (imported from "ilha") is distinct from the .effect() builder method — see effect(fn) above.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close