Import the browser primitives you need. Function-valued text and non-event DOM props subscribe to reactive values; event handlers run on their events. Exports marked internal belong to compiler or framework integration and are labeled separately below.
Read the usage guide or all entry points.
113 exports · published 3.0.0 declarations
import { $ } from '@getforma/core';Values and functions
$#
declare function $<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T | null;Select a single element by CSS selector. Returns null if not found.
const btn = $<HTMLButtonElement>('.submit-btn');
const inner = $('span', container);$$#
declare function $$<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T[];Select all elements matching a CSS selector, returned as a plain array.
const items = $$<HTMLLIElement>('ul > li');
items.forEach((li) => li.classList.add('active'));activateIslands#
declare function activateIslands(registry: Record<string, (...args: never[]) => unknown>, root?: ParentNode): Promise<void>;Discover and activate all SSR-rendered islands on the page.
Each island is activated inside its own createRoot scope with try/catch isolation — a broken island never takes down its siblings.
Parameter registry Own component names with compiler-bound registrations.
Parameter root Requested subtree (defaults to document). Identity and state authority are always frozen across the original Document. A shadow-only or later-injected host has no original claim.
Returns Resolves when requested load work has activated, failed in isolation, or been disposed. Existing pending loads are included; visible, idle and interaction work completes its part once its trigger is armed. Preflight remains synchronous; the function itself is not async. Load work starts in a timer unless first input promotes it sooner.
Verified by: src/dom/tests/activate.test.ts > "error in island 0 does not prevent island 1 from activating"
addClass#
declare function addClass(el: HTMLElement, ...classes: string[]): void;Add one or more CSS classes to an element.
batch#
declare function batch(fn: () => void): void;Group multiple signal updates so that effects only run once after all updates have been applied.
batch(() => {
setA(1);
setB(2);
// effects that depend on A and B won't run yet
});
// effects run here, onceBatches are nestable — only the outermost batch triggers the flush.
browserOnly#
declare function browserOnly<P extends object>(component: (props: P) => unknown): BrowserOnlyComponent<P>;Mark a component as rendering in the browser alone.
import { activateIslands, browserOnly } from '@getforma/core';
// chart.tsx: declared once, at the top level of a module.
export const Chart = browserOnly((props: { title: string }) => <canvas aria-label={props.title} />);
// The page renders <Chart title="Sales" /> where the chart goes; the server
// sends its empty host and the matching declared head-state occurrence.
// The entry registers it under the name it is rendered with:
activateIslands({ Chart });activateIslands finds the host empty and renders the component in its
place, with its validated declared props. Called as a component, in
a client-rendered page, it renders component(props). Where there is no
document (Node rendering), it renders nothing: the subtree is the browser's.
Verified by: src/dom/tests/browser-only.test.ts
Related declarations: BrowserOnlyComponent
children#
declare function children<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[];Get direct child elements, optionally filtered by a CSS selector.
cleanup#
declare function cleanup(el: Element): void;Remove all event listeners previously attached via h() on the given element.
Calls AbortController.abort() for the element, which automatically removes
every listener that was registered with its signal. The controller is then
deleted so a fresh one is created if the element is reused.
closest#
declare function closest<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector: string): T | null;Find the closest ancestor matching a CSS selector.
createBus#
declare function createBus<T extends Record<string, unknown> = Record<string, unknown>>(): EventBus<T>;Create a typed event bus for publish/subscribe messaging.
type Events = { save: { id: number }; delete: { id: number } };
const bus = createBus<Events>();
const unsub = bus.on('save', (payload) => console.log(payload.id));
bus.emit('save', { id: 42 });
unsub();Related declarations: EventBus
createComputed#
declare function createComputed<T>(fn: (previousValue?: T) => T): () => T;Create a lazy, cached computed value.
Note: Unlike SolidJS's createComputed (which is an eager synchronous side effect), this is a lazy cached derivation — equivalent to SolidJS's createMemo. Both createComputed and createMemo in FormaJS are identical.
The getter receives the previous value as an argument, enabling efficient diffing patterns without a separate signal:
const [count, setCount] = createSignal(0);
const doubled = createComputed(() => count() * 2);
console.log(doubled()); // 0
setCount(5);
console.log(doubled()); // 10With previous value (for diffing):
const changes = createComputed((prev) => {
const next = items();
if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);
return next;
});createContext#
declare function createContext<T>(defaultValue: T): Context<T>;Create a new context with a default value.
const ThemeCtx = createContext('light');Related declarations: Context
createEffect#
declare function createEffect(fn: () => void | (() => void)): () => void;createErrorBoundary#
declare function createErrorBoundary(tryFn: () => Node, catchFn: (error: Error, retry: () => void) => Node): DocumentFragment;Wrap a render function with error recovery.
const fragment = createErrorBoundary(
() => h('div', null, riskyComponent()),
(error, retry) => h('div', { class: 'error' },
h('p', null, `Error: ${error.message}`),
h('button', { onClick: retry }, 'Retry'),
),
);If tryFn throws, catchFn is called with the error and a retry function.
Calling retry() re-runs tryFn.
createHistory#
declare function createHistory<T>(source: [
get: () => T,
set: (v: T) => void
], options?: {
maxLength?: number;
}): HistoryControls<T>;Create undo/redo history tracking for a signal.
const [count, setCount] = createSignal(0);
const h = createHistory([count, setCount], { maxLength: 50 });
setCount(1);
setCount(2);
h.undo(); // count() === 1
h.redo(); // count() === 2
h.canUndo(); // true (reactive)Limitation: the source must be a signal whose getter returns a stable
value (a primitive or a stable object reference). A createStore slice whose
getter returns a fresh proxy on every read is not supported: the undo/redo
echo guard compares by identity, so a new proxy each read makes every
undo/redo look like an external change and clears the redo history. Track
store state with setState/direct mutation, not createHistory.
Related declarations: HistoryControls
createKeyedRows#
declare function createKeyedRows<T>(initial: readonly T[], keyFn: (item: T) => string | number): KeyedRows<T>;Store items by canonical key. Item replacement notifies only that item's readers; structural replacement validates the entire candidate before publishing any change. Inputs are plain data: direct external mutation is not a notifying replacement.
Related declarations: KeyedRows
createList#
declare function createList<T>(items: KeyedRows<T>, renderFn: (item: () => T, index: () => number) => HTMLElement): DocumentFragment;
declare function createList<T>(items: () => T[], keyFn: (item: T) => string | number, renderFn: (item: T, index: () => number) => HTMLElement | string | number, options?: CreateListOptions): DocumentFragment;Create a reactively-bound list of DOM elements with keyed reconciliation.
Returns a DocumentFragment containing two comment markers
(<!--forma-list-start--> and <!--forma-list-end-->) that delimit the
list's range in the DOM. All managed elements live between these markers.
This avoids a wrapper <div> which would break <table>, <ul>,
<select> semantics and pollute the DOM.
When the items signal changes, only the minimal set of DOM mutations is
performed using the LIS algorithm:
- New keys: create elements via
renderFn - Removed keys: remove elements from DOM
- Moved keys: reorder elements using minimum moves (n - LIS)
- Same keys: keep row nodes and update index signals
(or re-render row content when
updateOnItemChange: 'rerender')
Parameter items Signal getter returning the current array of items.
Parameter keyFn Extracts a unique key from each item.
Parameter renderFn Renders a single item. Receives the item and a reactive index getter.
Returns A DocumentFragment to insert into the DOM. The fragment includes start/end comment markers and any initial list items.
const frag = createList(
todos,
(t) => t.id,
(todo, index) => h('li', null, () => `${index() + 1}. ${todo.text}`),
);
container.appendChild(frag);Related declarations: CreateListOptions, KeyedRows
createMemo#
declare const createMemo: typeof createComputed;Create a memoized computed value.
Identical to createComputed — provided for React/SolidJS familiarity.
Note: Unlike SolidJS's createComputed (which is an eager synchronous side effect), both createComputed and createMemo in FormaJS are lazy cached derivations — equivalent to SolidJS's createMemo. They are identical.
The computation runs lazily and caches the result. It only recomputes when a signal it reads during computation changes.
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
console.log(doubled()); // 0
setCount(5);
console.log(doubled()); // 10Related declarations: createComputed
createPortal#
declare function createPortal(children: () => Node, target?: Element | string): Comment;Render content into an external DOM container.
const modal = createPortal(
() => h('div', { class: 'modal' }, 'Modal content'),
document.body,
);Returns a comment node placeholder. The actual content is rendered into the target container. Cleanup removes content from target.
createReducer#
declare function createReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S): [
state: SignalGetter<S>,
dispatch: Dispatch<A>
];Create a reducer — predictable state updates via dispatched actions.
The reducer function must be pure: (state, action) => newState.
Returns a [state, dispatch] tuple.
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };
const [count, dispatch] = createReducer(
(state: number, action: Action) => {
switch (action.type) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return 0;
}
},
0,
);
dispatch({ type: 'increment' }); // count() === 1
dispatch({ type: 'increment' }); // count() === 2
dispatch({ type: 'reset' }); // count() === 0Related declarations: Dispatch, SignalGetter
createRef#
declare function createRef<T>(initialValue: T): Ref<T>;Create a mutable ref container.
Unlike signals, writing to .current does NOT trigger effects.
Use when you need a stable reference across reactive scopes.
const timerRef = createRef<number | null>(null);
createEffect(() => {
timerRef.current = setInterval(tick, 1000);
onCleanup(() => clearInterval(timerRef.current!));
});
// DOM ref pattern:
const elRef = createRef<HTMLElement | null>(null);
h('div', { ref: (el) => { elRef.current = el; } });Related declarations: Ref
createResource#
declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S, info: ResourceFetcherInfo) => Promise<T>, options?: ResourceOptions<T>): Resource<T>;Create an async resource that fetches data reactively.
When source changes, the fetcher re-runs automatically.
Provides reactive loading and error signals.
const [userId, setUserId] = createSignal(1);
const user = createResource(
userId, // source signal
(id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher
);
internalEffect(() => {
if (user.loading()) console.log('Loading...');
else if (user.error()) console.log('Error:', user.error());
else console.log('User:', user());
});
setUserId(2); // automatically refetchesWithout a source signal (static fetch):
const posts = createResource(
() => true, // constant source — fetches once
() => fetch('/api/posts').then(r => r.json()),
);Related declarations: Resource, ResourceFetcherInfo, ResourceOptions, SignalGetter
createRoot#
declare function createRoot<T>(fn: (dispose: () => void) => T): T;Create a reactive root scope.
All effects created (via createEffect) inside the callback are tracked
at both the reactive graph level (via alien-signals effectScope) and the
userland level (via registerDisposer). The returned dispose function
tears down everything.
Roots created inside another root are automatically owned by the parent. When the parent is disposed, this root is disposed too.
const dispose = createRoot(() => {
createEffect(() => console.log(count()));
createEffect(() => console.log(name()));
});
// later: dispose() stops both effectscreateShow#
declare function createShow(when: () => unknown, thenFn: () => Branch, elseFn?: () => Branch): DocumentFragment;Conditionally render content based on a reactive boolean.
const [show, setShow] = createSignal(true);
const fragment = createShow(
show,
() => h('div', null, 'Visible!'),
() => h('div', null, 'Hidden fallback'),
);
container.appendChild(fragment);Returns a DocumentFragment with comment markers. The content between
markers is swapped reactively when when() changes.
Related declarations: Branch
createSignal#
declare function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [
get: SignalGetter<T>,
set: SignalSetter<T>
];Create a reactive signal.
const [count, setCount] = createSignal(0);
console.log(count()); // 0
setCount(1);
setCount(prev => prev + 1);With custom equality:
const [pos, setPos] = createSignal(
{ x: 0, y: 0 },
{ equals: (a, b) => a.x === b.x && a.y === b.y },
);Related declarations: SignalGetter, SignalOptions, SignalSetter
createStore#
declare function createStore<T extends object>(initial: T): [
get: T,
set: StoreSetter<T>
];Create a deep reactive store.
Returns a tuple of [getter proxy, setter function].
The getter proxy tracks reads at every property path via dedicated signals.
Setting a property (either via setState() or direct mutation like
state.user.name = 'Bob') only notifies effects that actually read that
specific path.
const [state, setState] = createStore({
count: 0,
user: { name: 'Alice', age: 30 },
items: [{ text: 'Buy milk', done: false }],
});
// Fine-grained reads
state.user.name; // tracked at path "user.name"
state.items[0].done; // tracked at path "items.0.done"
// Setter API (batched)
setState({ count: 1 });
setState(prev => ({ count: prev.count + 1 }));
// Direct mutation (via Proxy set trap)
state.user.name = 'Bob'; // only "user.name" subscribers notified
state.items[0].done = true;
// Array mutations
state.items.push({ text: 'Walk dog', done: false });Limitation: Object.keys(state), for...in, and spread ({...state})
are NOT reactive. Adding or removing a property will not trigger effects
that iterated over keys. Use signals or explicit arrays for collections
that need to react to membership changes.
Related declarations: StoreSetter
createSuspense#
declare function createSuspense(fallback: () => Node, children: () => Node): DocumentFragment;Create a Suspense boundary that shows fallback content while async resources inside the children function are loading.
Uses comment markers: <!--forma-suspense--> / <!--/forma-suspense-->
When all resources resolve, the fallback is swapped for the real content. If a resource errors, the fallback remains (pair with createErrorBoundary for errors).
const frag = createSuspense(
() => h('div', null, 'Loading...'),
() => {
const data = createResource(source, fetcher);
return h('div', null, () => data()?.name ?? '');
},
);
container.appendChild(frag);createSwitch#
declare function createSwitch<T>(value: () => T, cases: SwitchCase<T>[], fallback?: () => Branch): DocumentFragment;Multi-branch conditional rendering with node caching.
const [tab, setTab] = createSignal('home');
const fragment = createSwitch(tab, [
{ match: 'home', render: () => h('div', null, 'Home page') },
{ match: 'about', render: () => h('div', null, 'About page') },
{ match: 'contact', render: () => h('div', null, 'Contact page') },
], () => h('div', null, '404'));Previously rendered branches are cached — switching back to a tab re-inserts the cached node without re-rendering.
Related declarations: Branch, SwitchCase
createText#
declare function createText(value: string | (() => string)): Text;Create a Text node that is either static or reactively bound.
- If
valueis a string, creates a plain Text node. - If
valueis a function (signal getter), creates a Text node and sets up an effect that auto-updatestextContentwhenever the signal changes.
The text is written under rule 1 (normalizeDomText): CRLF and CR become
LF and NUL becomes U+FFFD, as the server's walker writes the same value.
Verified by: src/dom/tests/normalize-text.test.ts > "createText writes $id normalized, static and reactive"
Returns The Text node.
createUnownedRoot#
declare function createUnownedRoot<T>(fn: (dispose: () => void) => T): T;Create a reactive root that is NOT owned by any parent root.
Use this for roots that must outlive their lexical parent: top-level mount points, island hydration, test harnesses, etc.
createRoot(() => {
// This root outlives the parent even though it's nested:
createUnownedRoot((dispose) => {
// effects here survive parent disposal
});
});deactivateAllIslands#
declare function deactivateAllIslands(root?: ParentNode): void;Dispose ALL active islands under a root element (or the whole document).
Use this when swapping module content — e.g., replacing the contents of
a <forma-stage> Shadow DOM during AI generation. Prevents leaked effects
and event listeners from accumulating across swaps.
root is a ParentNode so a ShadowRoot is accepted — the documented
forma-stage case did not typecheck under the previous Element | Document,
and it takes the same argument as activateIslands.
deactivateIsland#
declare function deactivateIsland(el: HTMLElement): void;Dispose a single island, tearing down its reactive root and all effects.
Safe to call multiple times (idempotent). Sets data-forma-status to
"disposed" so the island can be distinguished from active/error states.
Verified by: src/dom/tests/deactivate.test.ts > "is idempotent — double disposal does not throw" Verified by: src/dom/tests/deactivate.test.ts > "stops effects from running after disposal"
defineComponent#
declare function defineComponent(setupOrDef: SetupFn | ComponentDef): () => HTMLElement | DocumentFragment;Define a component from a setup function or definition object. Returns a factory function that, when called, produces a DOM element with attached lifecycle and disposal logic.
The setup function runs ONCE per factory call. Reactivity is driven by signals, not by re-running setup.
Related declarations: ComponentDef, SetupFn
delegate#
declare function delegate<K extends keyof HTMLElementEventMap>(container: HTMLElement | Document, selector: string, event: K, handler: (e: HTMLElementEventMap[K], matchedEl: HTMLElement) => void, options?: AddEventListenerOptions): () => void;Attach a single event listener on a parent that fires for children matching a CSS selector. Efficient for large/dynamic lists — one listener instead of one per child.
const unsub = delegate(ul, 'li', 'click', (e, li) => {
console.log('Clicked:', li.textContent);
});Parameter container The parent element or document to listen on.
Parameter selector CSS selector to match against delegated targets.
Parameter event DOM event name (e.g. 'click', 'input').
Parameter handler Called with the event and the matched child element.
Parameter options Standard addEventListener options (capture, passive, etc.).
Returns An unsubscribe function that removes the listener.
disposeComponent#
declare function disposeComponent(dom: HTMLElement | DocumentFragment): void;Dispose a component that was created via defineComponent. This runs all onUnmount callbacks and disposes all tracked effects.
fragment#
declare function fragment(...children: unknown[]): DocumentFragment;Create a DocumentFragment from children.
fragment(
h('li', null, 'one'),
h('li', null, 'two'),
)Fragment#
declare const Fragment: (props: {
children?: unknown;
}) => DocumentFragment;Symbol used as JSX Fragment factory. h(Fragment, null, ...children) returns DocumentFragment.
Typed as a callable for TypeScript's JSX checker — at runtime it's a symbol
that h() detects via tag === Fragment. esbuild transforms <>...</> into
h(Fragment, null, ...) which never actually calls Fragment.
getBatchDepth#
export declare function getBatchDepth(): number;getSignalName#
declare function getSignalName(fn: unknown): string | undefined;Debug name of a signal getter, if one was provided (dev-only).
h#
declare function h(tag: (props: Record<string, unknown>) => unknown, props?: Record<string, unknown> | null, ...children: unknown[]): Node;
declare function h(tag: typeof Fragment, props?: null, ...children: unknown[]): DocumentFragment;
declare function h(tag: string, props?: ElementProps | null, ...children: unknown[]): HTMLElement;Related declarations: ElementProps, Fragment
hydrateIsland#
declare function hydrateIsland(component: () => unknown, target: Element): Element;Validate the component's description and attach it to the existing target. Call inside createRoot so the caller owns all installed work. A marked element target is the component's root; an unmarked target contains it. Root Fragment/Show/List descriptions consume the target's exact children.
Successful adoption preserves every initial node and attribute. A coded mismatch reports through onError with source 'hydration' and returns the same target. Initial user exceptions are cleaned up and rethrown unchanged. This direct API has no compiled setter authority for early edited bindings.
inject#
declare function inject<T>(ctx: Context<T>): T;Read the current value of a context. Returns the most recently provided value, or the default if none was provided.
const theme = inject(ThemeCtx); // 'dark' if provided, else 'light'Related declarations: Context
isComputed#
export declare function isComputed(fn: () => void): boolean;isEffect#
export declare function isEffect(fn: () => void): boolean;isEffectScope#
export declare function isEffectScope(fn: () => void): boolean;isSignal#
export declare function isSignal(fn: () => void): boolean;mount#
declare function mount(component: () => HTMLElement | DocumentFragment, container: HTMLElement | string): () => void;Mount a component into a DOM container.
All effects created during the component's render are tracked via
createRoot() and automatically disposed when unmount is called.
A deployment mismatch preserves the container and returns an inert disposer.
Otherwise data-forma-ssr on the container throws FORMA_E_MOUNT_SSR before
component/root/effect creation and without changing the server DOM.
Use compiler-declared activateIslands for SSR interaction. Normal Browser
mounting and the separate public hydrateIsland API are unchanged.
Parameter component A function returning an HTMLElement or DocumentFragment.
Parameter container An HTMLElement or a CSS selector string.
Returns An unmount function that removes the DOM and disposes all effects.
const unmount = mount(() => h('h1', null, 'Hello'), '#app');
// later…
unmount();nextSibling#
declare function nextSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null;Find the next sibling element, optionally matching a CSS selector.
on#
declare function on<T, U>(deps: () => T, fn: (value: T, prev: T | undefined) => U, options?: {
defer?: boolean;
}): () => U | undefined;Create a tracked effect body that only fires when specific dependencies change.
Wraps a function so that only the deps signals are tracked.
All signal reads inside fn are untracked (won't cause re-runs).
Use with createEffect:
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(2);
// Only re-runs when `a` changes, NOT when `b` changes:
createEffect(on(a, (value, prev) => {
console.log(`a changed: ${prev} → ${value}, b is ${b()}`);
}));
setA(10); // fires: "a changed: 1 → 10, b is 2"
setB(20); // does NOT fireMultiple dependencies:
createEffect(on(
() => [a(), b()] as const,
([aVal, bVal], prev) => { ... }
));onCleanup#
declare function onCleanup(fn: () => void): void;Register a cleanup function in the current reactive scope. The cleanup runs before the effect re-executes and on disposal.
More composable than returning a cleanup from the effect function, since it can be called from helper functions.
createEffect(() => {
const timer = setInterval(tick, 1000);
onCleanup(() => clearInterval(timer));
});onError#
declare function onError(handler: ErrorHandler): () => void;Install a global error handler for FormaJS reactive errors. Called when effects, computeds, or event handlers throw. Multiple handlers may be registered; returns an unsubscribe function that removes only this handler.
const off = onError((err, info) => {
Sentry.captureException(err, { tags: { source: info?.source } });
});
// later: off();Related declarations: ErrorHandler
onIntersect#
declare function onIntersect(el: HTMLElement, handler: (entry: IntersectionObserverEntry) => void, options?: IntersectionObserverInit): () => void;Observe element visibility changes via IntersectionObserver.
Returns A disconnect function that stops observing.
onKey#
declare function onKey(combo: KeyCombo, handler: (e: KeyboardEvent) => void, options?: KeyOptions): () => void;Listen for a keyboard shortcut and invoke a handler when it fires.
Supports modifier keys: ctrl, shift, alt, meta/cmd.
const unsub = onKey('ctrl+s', (e) => save());
const unsub2 = onKey('escape', () => close(), { target: modal });Parameter combo Key combination string (e.g. 'ctrl+s', 'shift+enter').
Parameter handler Called when the combo is pressed.
Parameter options Optional target element and preventDefault behavior.
Returns An unsubscribe function that removes the listener.
Related declarations: KeyCombo, KeyOptions
onMount#
declare function onMount(fn: () => void | CleanupFn): void;Register a callback that runs after the component's setup completes. If the callback returns a function, that function is called on unmount. Must be called inside a setup function.
Related declarations: CleanupFn
onMutation#
declare function onMutation(el: HTMLElement, handler: (mutations: MutationRecord[]) => void, options?: MutationObserverInit): () => void;Observe DOM mutations (child additions/removals, attribute changes) via
MutationObserver. Defaults to { childList: true, subtree: true }.
Returns A disconnect function that stops observing.
onResize#
declare function onResize(el: HTMLElement, handler: (entry: ResizeObserverEntry) => void): () => void;Observe element size changes via ResizeObserver.
Returns A disconnect function that stops observing.
onUnmount#
declare function onUnmount(fn: () => void): void;Register a callback that runs when the component is disposed. Must be called inside a setup function.
parent#
declare function parent<T extends HTMLElement = HTMLElement>(el: HTMLElement): T | null;Get the parent element, or null if detached.
persist#
declare function persist<T>(source: [
get: () => T,
set: (v: T) => void
], key: string, options?: PersistOptions<T>): () => void;Persist a signal's value to storage.
On creation, reads the stored value (if any) and hydrates the signal. Then sets up an effect to write to storage whenever the signal changes.
const [theme, setTheme] = createSignal('light');
persist([theme, setTheme], 'app:theme');
setTheme('dark'); // auto-saved to localStorage under key 'app:theme'Related declarations: PersistOptions
prevSibling#
declare function prevSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null;Find the previous sibling element, optionally matching a CSS selector.
provide#
declare function provide<T>(ctx: Context<T>, value: T): void;Provide a value for a context. The value is pushed onto the context's stack and will be returned by inject() until it is removed via unprovide() or overridden by a nested provide().
provide(ThemeCtx, 'dark');Related declarations: Context
reconcileList#
declare function reconcileList<T>(parent: Node, oldItems: T[], newItems: T[], oldNodes: Node[], keyFn: (item: T) => string | number, createFn: (item: T) => Node, updateFn: (node: Node, item: T) => void, beforeNode?: Node | null, hooks?: ListTransitionHooks): ReconcileResult<T>;Reconcile a DOM parent's children to match a new array of items. Uses keyed reconciliation with LIS for minimum DOM operations.
For small lists (< 32 items), uses a flat array scan path that avoids Map/Set hash overhead and provides better cache locality.
Parameter parent - The container DOM element
Parameter oldItems - Previous array (from last reconciliation)
Parameter newItems - New array to render
Parameter oldNodes - Previous DOM nodes array
Parameter keyFn - Extracts a unique key from each item
Parameter createFn - Creates a new DOM node for an item
Parameter updateFn - Updates an existing DOM node with new item data
Parameter beforeNode - Optional boundary marker. When provided, new nodes are inserted before this node instead of appended to parent. This allows the reconciler to operate within a range delimited by comment markers.
Returns Object with new nodes array and items array for next call
Related declarations: ListTransitionHooks, ReconcileResult
removeClass#
declare function removeClass(el: HTMLElement, ...classes: string[]): void;Remove one or more CSS classes from an element.
runWithOwner#
declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;Run fn with owner installed as the current owner, restoring the previous
owner afterwards. Lets effects/roots created asynchronously (after the
original root callback returned) still be owned and disposed.
Related declarations: Owner
sanitizePropsDeep#
declare function sanitizePropsDeep<T>(props: T): T;Strip __proto__ / constructor / prototype keys from an island props
object at every depth, in place.
The compiled state decoder projects only declared safe fields into fresh objects. This standalone helper remains available for application data that reaches code by other means; it is not an SSR state fallback.
It runs after JSON.parse rather than as its reviver: V8 applies a reviver
recursively and overflows the stack near 5,000 levels of nesting, where
JSON.parse alone parses 100,000. This pass is iterative with an explicit
stack and a WeakSet, so neither deeply nested nor cyclic props can overflow
the stack or loop forever.
Throws TypeError if an own forbidden key cannot be deleted, including on a frozen or sealed object. Earlier deletions remain in place when it throws; callers must not use the partially sanitized input after a refusal.
Verified by: src/dom/tests/activate-isolation.test.ts > "sanitizePropsDeep strips forbidden keys at every depth" Verified by: src/dom/tests/activate-isolation.test.ts > "sanitizePropsDeep terminates on cyclic props"
setAttr#
declare function setAttr(el: HTMLElement, attrs: Record<string, string | TrustedHtml | boolean | null> & {
srcdoc?: TrustedHtml | false | null;
}): void;Set or remove multiple attributes on an element.
false/nullremoves the attribute.truesets it as a boolean attribute (empty string).- A string sets the attribute value.
Related declarations: TrustedHtml
setHTMLUnsafe#
declare function setHTMLUnsafe(el: HTMLElement, html: TrustedHtml): void;Set raw HTML on an element. No sanitization is performed.
Prefer setText() for user-controlled content. Only use this when you
trust the HTML source (e.g., server-rendered markup you control), and wrap
it in trustedHtml() (DR-19). Plain strings are refused before the sink.
Related declarations: TrustedHtml
setStyle#
declare function setStyle(el: HTMLElement, styles: Partial<CSSStyleDeclaration>): void;Apply multiple inline styles to an element at once.
setStyle(el, { opacity: '0', transform: 'translateY(-10px)' });setText#
declare function setText(el: HTMLElement, text: string): void;Set an element's text content. Safe for user-controlled strings.
siblings#
declare function siblings<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[];Get sibling elements (excluding the element itself), optionally filtered.
svg#
declare function svg<T extends Node>(build: () => T): T;Run build() with SVG as the namespace of the content being built, so the
tags that are both HTML and SVG (a, title, script, style, font)
are created in SVG.
h() builds a child before its parent exists, so in a hand-written tree
such a tag is HTML unless the tree is built inside svg(). Where the parent
is known, h() takes its namespace without svg(): a reactive child, a
control-flow helper's renders once it is in the document, a portal into an
SVG, mount() into an SVG element, and what adoption creates. Inside
svg(), build a foreignObject's, desc's or title's content outside it,
where it is HTML.
const icon = svg(() => h('svg', { viewBox: '0 0 10 10' }, h('a', { href: '#' })));Verified by: src/dom/tests/namespace-resolution.test.ts > "a title or an a passed to h() before its svg parent exists stays HTML, and is SVG inside svg()"
template#
declare function template(html: TrustedHtml): Node;Create a reusable template node from markup. A raw sink: the markup is
parsed as it is, unescaped, so pass trustedHtml() (DR-19). Plain strings
are refused before parsing or returning a cached template.
The returned node is a cached prototype. Callers must call
.cloneNode(true) to get a unique DOM tree for each component instance.
Related declarations: TrustedHtml
templateMany#
declare function templateMany(html: TrustedHtml): DocumentFragment;Create a reusable template DocumentFragment from markup, a raw sink as template is.
Used when the template has multiple root nodes. The returned fragment
is a cached prototype; callers must call .cloneNode(true).
Related declarations: TrustedHtml
toggleClass#
declare function toggleClass(el: HTMLElement, className: string, force?: boolean): boolean;Toggle a CSS class on an element. Returns the resulting state.
trackDisposer#
declare function trackDisposer(dispose: () => void): void;Track an effect disposal within the current component's lifecycle. Call this inside a setup function to ensure effects are cleaned up when the component is disposed.
trigger#
export declare function trigger(fn: () => void): void;trustedHtml#
declare function trustedHtml(markup: string): TrustedHtml;Vouch for markup as HTML a raw sink may write unescaped: the one raw
constructor in TypeScript (DR-19), so a search for its name finds every raw
entry. Nothing is sanitised: pass only markup you control, never a value an
end user supplied.
h('div', { dangerouslySetInnerHTML: { __html: trustedHtml(renderedMarkdown) } });Verified by: src/dom/tests/trusted-html.test.ts > "every raw sink writes a trustedHtml() value without a warning, and the DOM holds its markup" Verified by: src/dom/tests/trusted-html.test.ts > "trustedHtml() returns a TrustedHTML from the forma policy where the browser has Trusted Types"
Related declarations: TrustedHtml
unprovide#
declare function unprovide<T>(ctx: Context<T>): void;Remove the most recent provided value for a context. Used during component teardown to restore the previous scope.
unprovide(ThemeCtx);Related declarations: Context
untrack#
declare function untrack<T>(fn: () => T): T;Execute a function without tracking signal reads.
Any signals read inside fn will NOT become dependencies of the
surrounding effect or computed.
createEffect(() => {
const a = count(); // tracked — effect re-runs when count changes
const b = untrack(() => other()); // NOT tracked — effect ignores other changes
});value#
declare function value<T>(v: T): () => T;Wrap a value so the setter treats it as a literal value, not a functional updater.
When T is itself a function type, passing a function to the setter is
ambiguous -- it looks like a functional update (prev => next). Use
value() to disambiguate:
const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));
// BUG: interpreted as a functional update -- calls the arrow with prev
// setFn(() => console.log('b'));
// Correct: wraps in a thunk so the setter stores it as-is
setFn(value(() => console.log('b')));Exported types
CleanupFn#
type CleanupFn = () => void;A teardown function that disposes effects and cleans up resources.
ComponentDef#
interface ComponentDef {
setup: SetupFn;
name?: string;
}Definition object passed to defineComponent.
Related declarations: SetupFn
Member notes
setup
The setup function that builds the component's DOM and reactive bindings.
name
Optional debug name. It prefixes the source of any error reported from
this component's lifecycle (onMount, onUnmount, disposers), so an
onError handler can say which component failed.
Context#
interface Context<T> {
readonly id: symbol;
readonly defaultValue: T;
}A typed dependency injection context created by createContext.
Member notes
id
Unique identifier for this context.
defaultValue
Value returned when no provider is active.
CreateListOptions#
interface CreateListOptions {
updateOnItemChange?: 'none' | 'rerender';
}Member notes
updateOnItemChange
How to handle same-key items whose object identity changed.
- 'none' (default): keep the existing row node for maximum throughput.
- 'rerender': replace changed rows in a fresh reactive scope. Unchanged item objects and keyed reorders retain their existing DOM and state.
Dispatch#
type Dispatch<A> = (action: A) => void;A function that dispatches an action to a reducer.
ErrorHandler#
type ErrorHandler = (error: unknown, info?: {
source?: string;
}) => void;Callback signature for a global onError handler.
EventBus#
interface EventBus<T extends Record<string, unknown>> {
on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void;
once<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void;
emit<K extends keyof T>(event: K, payload: T[K]): void;
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void;
clear(): void;
}A typed publish/subscribe event bus.
The type parameter T maps event names to their payload types,
ensuring type-safe event emission and subscription.
Member notes
on
Subscribe to an event. Returns an unsubscribe function.
once
Subscribe to an event, automatically unsubscribing after the first firing.
emit
Emit an event to all current subscribers.
off
Remove a specific handler from an event.
clear
Remove all handlers for all events.
HistoryControls#
interface HistoryControls<T> {
undo: () => void;
redo: () => void;
canUndo: () => boolean;
canRedo: () => boolean;
history: () => T[];
cursor: () => number;
clear: () => void;
destroy: () => void;
}Undo/redo controls returned by createHistory.
Member notes
undo
Undo the last change, restoring the previous value.
redo
Redo the last undone change.
canUndo
Reactive getter: true if undo is available.
canRedo
Reactive getter: true if redo is available.
history
Reactive getter: the full history stack (past + current + future).
cursor
Reactive getter: current position in the history stack (0-based).
clear
Clear all history, keeping only the current value.
destroy
Stop tracking the source signal and release the history stack.
IslandHydrateFn#
type IslandHydrateFn = (el: HTMLElement, props: Record<string, unknown> | null) => unknown;Function that hydrates an island.
Parameter el The root element of the island ([data-forma-island]).
Useful for layout measurement, focus management, CSS class
toggling, third-party library init, or reading extra data-*
attributes from the server-rendered shell.
Parameter props Fresh declared props from the checked head state. Compiler registrations pass this object to the original unary component.
Returns A component tree (from h() calls) for descriptor-based
hydration, or undefined for imperative islands that set up
their own effects.
KeyedRows#
interface KeyedRows<T> {
readonly [keyedRowsBrand]: true;
set(key: string | number, next: T): void;
replace(nextItems: readonly T[]): void;
}An indexed collection. set replaces an existing item; replace changes membership/order.
KeyOptions#
interface KeyOptions {
target?: EventTarget;
preventDefault?: boolean;
}Options for the onKey keyboard shortcut handler.
Member notes
target
Element or document to listen on. Defaults to document.
preventDefault
Whether to call e.preventDefault(). Defaults to true.
ListTransitionHooks#
interface ListTransitionHooks {
onInsert?: (node: Node) => void;
onBeforeRemove?: (node: Node, done: () => void) => void;
}Animation hooks for list transitions.
Member notes
onInsert
Called after a new node is inserted into the DOM.
onBeforeRemove
Called before a node is removed. Call done() when the exit animation finishes.
Owner#
interface Owner {
disposers: (() => void)[];
}The minimal ownership unit: something that holds disposers. A RootScope
is an Owner with an extra alien-signals scope dispose. Effects use lightweight
child owners (no scopeDispose) to own their nested effects generation-by-generation.
Member notes
disposers
Disposers to run when this owner is torn down.
PersistErrorPhase#
type PersistErrorPhase = 'hydrate' | 'serialize' | 'write' | 'migrate';The phase in which a persist operation failed, passed to PersistOptions.onError.
PersistOptions#
interface PersistOptions<T> {
storage?: Storage;
serialize?: (v: T) => string;
deserialize?: (s: string) => T;
validate?: (v: unknown) => v is T;
version?: number;
migrate?: (oldValue: unknown, oldVersion: number) => T;
syncTabs?: boolean;
onError?: (err: unknown, phase: PersistErrorPhase) => void;
}Options for persist — storage backend, serialization, and validation.
Related declarations: PersistErrorPhase
Member notes
storage
Storage backend. Defaults to localStorage.
serialize
Custom serializer. Defaults to JSON.stringify.
deserialize
Custom deserializer. Defaults to JSON.parse.
validate
Optional validator — return true if the deserialized value is valid. Rejects corrupt/tampered data.
version
Current schema version. When set, values are stored in a versioned envelope.
migrate
Migrate stored data whose version is older than version. Runs before validate.
syncTabs
Re-hydrate when another tab writes this key. Defaults to true for localStorage.
onError
Report hydrate/serialize/write/migrate failures instead of swallowing them.
ReconcileResult#
interface ReconcileResult<T> {
nodes: Node[];
items: T[];
}Return value from reconcileList — the new nodes and items arrays.
Member notes
nodes
The DOM nodes currently in the list, in order.
items
The items array corresponding to the current nodes.
Ref#
interface Ref<T> {
current: T;
}A mutable container that does NOT trigger reactivity when written to.
Member notes
current
The stored value. Mutating this does not notify effects.
Resource#
interface Resource<T> {
(): T | undefined;
loading: SignalGetter<boolean>;
error: SignalGetter<unknown>;
refetch: () => void;
mutate: (value: T | undefined) => void;
}An async data resource with reactive loading and error state.
Related declarations: SignalGetter
Member notes
loading
True while the fetcher is running.
error
The error if the fetcher rejected (or undefined).
refetch
Manually refetch with the current source value.
mutate
Manually set the data (overrides fetcher result).
ResourceFetcherInfo#
interface ResourceFetcherInfo {
signal: AbortSignal;
}Info passed to a resource fetcher — carries the AbortSignal for this fetch.
Member notes
signal
Aborted when the source changes / the resource refetches or is disposed.
ResourceOptions#
interface ResourceOptions<T> {
initialValue?: T;
}Options for createResource.
Member notes
initialValue
Initial value before first fetch resolves.
SetupFn#
type SetupFn = () => HTMLElement | DocumentFragment;A function that runs once to build the component's DOM tree.
SignalGetter#
type SignalGetter<T> = () => T;A function that reads the current value of a signal.
SignalOptions#
interface SignalOptions<T> {
name?: string;
equals?: (prev: T, next: T) => boolean;
}Member notes
name
Debug name — attached to getter in dev mode for devtools inspection.
equals
Custom equality check. When provided, the setter will read the current
value and only update the signal if equals(prev, next) returns false.
Default: none (alien-signals uses strict inequality !== internally).
const [pos, setPos] = createSignal(
{ x: 0, y: 0 },
{ equals: (a, b) => a.x === b.x && a.y === b.y },
);
setPos({ x: 0, y: 0 }); // skipped — equals returns true
setPos({ x: 1, y: 0 }); // applied — equals returns falseSignalSetter#
type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;A function that updates a signal — accepts a value or an updater function.
StoreSetter#
type StoreSetter<T extends object> = (partial: Partial<T> | ((prev: T) => Partial<T>)) => void;Setter for a reactive store — accepts a partial update object or updater function.
SwitchCase#
interface SwitchCase<T> {
match: T;
render: () => Branch;
}A single branch for createSwitch.
Related declarations: Branch
Member notes
match
The value that activates this branch.
render
Factory function that renders this branch's DOM.
TrustedHtml#
interface TrustedHtml {
readonly __formaTrustedHtml: true;
}Markup vouched for by trustedHtml: a browser TrustedHTML where the
browser has Trusted Types, else Forma's frozen stand-in. Every raw sink takes
only this value. A TrustedHTML from an
application's own policy is accepted at run time too; TypeScript sees it as
this type only through a cast (value as unknown as TrustedHtml).
Member notes
__formaTrustedHtml
Type-level brand only: no value carries this property. It keeps a plain string, or any other object, from passing for trusted markup.
Internal integration exports
__adoptionChild#
declare function __adoptionChild<A extends unknown[], R>(receiver: undefined, component: (...args: A) => R, args: A): R;__adoptionConditional#
declare function __adoptionConditional<T, F>(test: () => unknown, consequent: () => T, alternate: () => F): T | F;__adoptionSignal#
declare function __adoptionSignal<T>(site: number, pair: [
SignalGetter<T>,
SignalSetter<T>
]): [
SignalGetter<T>,
SignalSetter<T>
];Related declarations: SignalGetter, SignalSetter
__adoptionWritable#
declare function __adoptionWritable<T>(read: () => T, get: SignalGetter<T>, codec: AdoptionCodec, site: number): () => T;Related declarations: AdoptionCodec, SignalGetter
__islandRegistration#
declare function __islandRegistration<P extends object>(component: (props: P) => unknown, metadata: IslandRegistrationMetadata): IslandHydrateFn;Related declarations: IslandHydrateFn, IslandRegistrationMetadata
getOwner#
declare function getOwner(): Owner | null;Related declarations: Owner
Referenced declaration types
These types appear in the published signatures but are not separate named exports from this entry point. They are shown to explain the contract, not as additional import paths.
AdoptionCodec#
type AdoptionCodec = 'string' | 'number' | 'boolean' | 'string-array';Private compiler hooks. The adoption planner owns the active sink.
Branch#
type Branch = Node | string | number | boolean | null | undefined;What a branch, case or row function may return.
BrowserOnlyComponent#
interface BrowserOnlyComponent<P> {
(props: P): unknown;
(el: HTMLElement, props: Record<string, unknown> | null): unknown;
}A browser-only component. Called with props, it renders Impl, as in a
client-rendered tree; registered in activateIslands, it is called with the
compiler-bound projected props and renders Impl from those props.
ElementProps#
type ElementProps = Record<string, unknown> & {
dangerouslySetInnerHTML?: {
__html: TrustedHtml;
} | (() => {
__html: TrustedHtml;
} | null | false) | null | false;
srcdoc?: TrustedHtml | (() => TrustedHtml | null | false) | null | false;
};Named raw props remain typed even though other attributes are extensible.
Related declarations: TrustedHtml
IslandRegistrationMetadata#
interface IslandRegistrationMetadata {
readonly h: string;
readonly schema: IslandSchema;
readonly mode: 0 | 1;
}Related declarations: IslandSchema
IslandSchema#
type IslandSchema = readonly (readonly {
readonly name: string;
readonly type: IslandSchemaType;
}[])[];Related declarations: IslandSchemaType
IslandSchemaType#
type IslandSchemaType = {
readonly kind: 'boolean' | 'string' | 'number' | 'date';
} | {
readonly kind: 'enum';
readonly variants: readonly string[];
} | {
readonly kind: 'array';
readonly element: IslandSchemaType;
} | {
readonly kind: 'optional';
readonly inner: IslandSchemaType;
} | {
readonly kind: 'object';
readonly object: number;
};Private compiler/state handshake. No DOM writes or component calls occur here.
KeyCombo#
type KeyCombo = string;Other entry points
@getforma/core/runtime · @getforma/core/runtime-hardened · @getforma/core/runtime-csp · @getforma/core/tc39 · @getforma/core/http · @getforma/core/storage · JSX namespace