Context

Creates a named global signal shared across all islands. Identical keys always return the same signal instance, making it the primary way to share reactive state between islands without prop drilling or a separate store.

Basic usage

import { 
const context: <T>(key: string, initial: T) => ContextSignal<T>
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" (read)
const theme: (value: string) => void (+1 overload)
theme
("dark"); // → sets to "dark" (write)

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: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
, {
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml
html
,
const context: <T>(key: string, initial: T) => ContextSignal<T>
context
} from "ilha";
const
const cartCount: ContextSignal<number>
cartCount
=
context<number>(key: string, initial: number): ContextSignal<number>
context
("cart.count", 0);
const
const CartButton: Island<Record<string, unknown>, Record<string, never>>
CartButton
=
const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.on<"button@click">(selectorOrCombined: "button@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, Record<string, never>, "click", Record<string, never>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> (+1 overload)
on
("button@click", () =>
const cartCount: (value: number) => void (+1 overload)
cartCount
(
const cartCount: () => number (+1 overload)
cartCount
() + 1))
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>>
render
(() =>
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml
html
` <button>Add to cart</button> `);
const
const CartBadge: Island<Record<string, unknown>, Record<string, never>>
CartBadge
=
const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>>
render
(() =>
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml
html
`<span>${
const cartCount: () => number (+1 overload)
cartCount
()}</span>`);

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

Using context in .bind()

Pass a context signal directly to .bind() to sync a form element across islands:

import 
const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
, {
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml
html
,
const context: <T>(key: string, initial: T) => ContextSignal<T>
context
} from "ilha";
const
const query: ContextSignal<string>
query
=
context<string>(key: string, initial: string): ContextSignal<string>
context
("search.query", "");
const
const SearchInput: Island<Record<string, unknown>, Record<string, never>>
SearchInput
=
const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.bind<string>(selector: string, externalSignal: ExternalSignal<string>): IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> (+1 overload)
bind
("input",
const query: ContextSignal<string>
query
)
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>>
render
(() =>
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml
html
`<input type="search" />`);
const
const SearchResults: Island<Record<string, unknown>, Record<string, never>>
SearchResults
=
const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>>
render
(() =>
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml
html
`<p>Results for: ${
const query: () => string (+1 overload)
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>
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: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
, {
const context: <T>(key: string, initial: T) => ContextSignal<T>
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<Record<string, unknown>, Record<string, never>>
Island
=
const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, Record<string, never>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>
effect
(() => {
var document: Document

window.document returns a reference to the document contained in the window.

MDN Reference

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 element for HTML documents).

MDN Reference

documentElement
.
HTMLOrSVGElement.dataset: DOMStringMap
dataset
["theme"] =
const theme: () => string (+1 overload)
theme
();
}) .
IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, Record<string, never>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, Record<string, never>>
render
(() => `<div>content</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.

Notes

  • Keys are global strings. Use namespaced keys like "app.theme" or "cart.count" to avoid accidental collisions across different parts of your app.
  • There is no way to delete or reset a context signal once created short of reloading the module.
  • 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().