ElementsKit is a toolkit of reactive primitives β signals, JSX, custom elements, and browser-API helpers. Import one at a time, compose them, or use any of them inside a UI framework (React, etc.).
Compose, donβt configure. Small focused APIs β signal, computed, on, fromEvent, async. Combine primitives instead of maintaining an overloaded interface.
Close to the platform. JSX compiles to document.createElement. promise extends Promise. Custom elements areHTMLElement. Thin or absent abstraction layers β no virtual DOM, no proxies, no build steps.
Predictable and explicit β no magic.signal/compose are reactive; nothing else is. No heuristic dependency tracking, no hidden subscriptions.
Designed for the AI age. Code is cheap; maintenance still isnβt. Primitives compose into higher-level blocks. Swap one block at a time instead of maintaining long lines of code.
Bundler-friendly. Every primitive is its own subpath β elements-kit/signals, elements-kit/utilities/media-query, elements-kit/integrations/react. Import only what you need.
Signals & Stores
Fine-grained reactive state. signal, computed, effect, and @reactive class fields β plain classes, no proxies. Shared across custom elements, React components, and plain scripts, all in sync.
Utilities
Browser-API signals β online, windowSize, createMediaQuery, on, fromEvent, observers, and more. Reactive wrappers, pay-per-import.
Async
async, promise, retry, createLocalStorage. Compose queries, mutations, persistence, and revalidation β no query library required.
Elements
JSX compiles to real document.createElement calls. Reactive props become live DOM bindings β no diffing, no reconciliation, no runtime overhead.
Custom Elements
Native HTMLElement subclasses enhanced with signals, JSX, and decorators. Usable in any HTML context β React, Vue, or plain HTML β no adapters needed.
Framework Integration
Bridge any signal into a UI framework via useSignal / useScope. React today, Svelte and Vue planned.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
Read: call with no arguments β returns the current value and
subscribes the active tracking context.
Write: call with a value β updates the signal and schedules
downstream effects if the value changed.
@example
constcount=signal(0);
count(); // β 0 (read)
count(1); // write β effects depending on count will re-run
count(); // β 1
signal(
props: ReactiveProps<{
initial?:number;
}>
props.
initial?: Computed<number |undefined>|undefined
initial() ??0);
const
constdoubled: () =>number
doubled=
computed<number>(getter: (previousValue?:number|undefined) => number): () => number
Creates a lazily-evaluated computed value.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
The getElementById() method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
The getElementById() method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
that is also callable as a signal: invoking it
(with no args) reads the current result, so it drops into any reactive
context that expects a zero-arg getter.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
that is also callable as a signal: invoking it
(with no args) reads the current result, so it drops into any reactive
context that expects a zero-arg getter.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
The getElementById() method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
The read-only target property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event.
A decorator that makes a class field reactive by automatically wrapping its value in a signal.
The field behaves like a normal property (get/set) but reactivity is tracked under the hood.
Any reads will subscribe to the signal and any writes will trigger updates.
@example
classCounter {
\@reactive() count:number=0;
}
constcounter=newCounter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes
@remarks β
Equivalent to manually creating a private signal and getter/setter:
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
A class decorator that automatically wires up observedAttributes and attributeChangedCallback
from a static [ATTRIBUTES] map.
The this type inside attribute handlers is automatically inferred from the decorated class.
@example
\@attributes
classMyElementextendsHTMLElement {
static [ATTRIBUTES] = {
count(this:MyElement, value:string|null) {
this.count =Number(value);
},
};
}
attributes
class
classCounterElement
CounterElementextends
var HTMLElement: {
new ():HTMLElement;
prototype:HTMLElement;
}
The HTMLElement interface represents any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.
A decorator that makes a class field reactive by automatically wrapping its value in a signal.
The field behaves like a normal property (get/set) but reactivity is tracked under the hood.
Any reads will subscribe to the signal and any writes will trigger updates.
@example
classCounter {
\@reactive() count:number=0;
}
constcounter=newCounter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes
@remarks β
Equivalent to manually creating a private signal and getter/setter:
classCounter {
#count=signal(0);
getcount() { returnthis.#count(); }
setcount(value) { this.#count(value); }
}
reactive()
CounterElement.count: number
count=0;
CounterElement.doubled: () => number
doubled=
computed<number>(getter: (previousValue?:number|undefined) => number): () => number
Creates a lazily-evaluated computed value.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
setup runs inside a detached effectScope. The returned node is appended
to target. Calling the returned unmount removes the node from the DOM,
disposes its Symbol.dispose hook (JSX-created elements carry one), and
tears down every effect / onCleanup registered inside setup.
The customElements read-only property of the Window interface returns a reference to the CustomElementRegistry object, which can be used to register new custom elements and get information about previously registered custom elements.
The define() method of the CustomElementRegistry interface adds a definition for a custom element to the custom element registry, mapping its name to the constructor which will be used to create it.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
@example
consta=signal(1);
constb=signal(2);
constsum=computed(() =>a() +b());
sum(); // β 3
a(10);
sum(); // β 12 (re-evaluated lazily)
computed,
functioneffect(fn: () =>void): () =>void
Creates a reactive side-effect that runs immediately and re-runs whenever
any signal or computed it read during its last execution changes.
Use
onCleanup
inside fn to register teardown logic that runs
before each re-execution and on final disposal.
If effect is called inside an effectScope or another effect, the
new effect is automatically owned by the outer scope and will be disposed
when the scope is disposed.
@param β fn - The side-effect body. Reactive reads inside this function
establish dependency links.
@returns β A disposal function. Call it to stop the effect and run any
registered cleanup.
@example
consturl=signal('/api/data');
conststop=effect(() => {
constcontroller=newAbortController();
fetch(url(), { signal: controller.signal });
onCleanup(() => controller.abort());
});
url('/api/other'); // previous fetch is aborted, new one starts
stop(); // final cleanup: abort the last fetch
effect } from"elements-kit/signals";
import {
functionuseSignal<T>(value: () =>T):T
Subscribe to any readable signal β writable or computed β returning its current value.
Accepts any zero-argument callable () => T, which includes both Signal<T> and
Computed<T>. Using () => T instead of Computed<T> prevents TypeScript from
picking the write overload of Signal<T> during type inference.
@template β T - The type of the signal value.
@param β value - A writable Signal<T> or a derived Computed<T>.
@returns β The current value, updated on every signal change.
Create a signal effect scope tied to a React component's lifetime.
All effects registered inside callback are grouped into a single scope. The scope β and every effect within it β is automatically stopped when the component unmounts.
If your callback returns a Computed<T> signal, the hook will always return its current value, updating reactively as dependencies change. If your callback returns void, the value will be undefined.
Returns the current value of the computed signal (or undefined).
Use this when you want to create multiple related effects at once without individually managing each one's lifecycle. All effects and cleanups inside the callback are automatically cleaned up on unmount.
@template β T - The type of the computed value (if any).
@param β callback - A function that registers one or more signal effects, optionally returning a Computed<T>.
@returns β value β the current value of the computed signal (or undefined).
Read: call with no arguments β returns the current value and
subscribes the active tracking context.
Write: call with a value β updates the signal and schedules
downstream effects if the value changed.
@example
constcount=signal(0);
count(); // β 0 (read)
count(1); // write β effects depending on count will re-run
count(); // β 1
signal(0);
const
constdoubled: () =>number
doubled=
computed<number>(getter: (previousValue?:number|undefined) => number): () => number
Creates a lazily-evaluated computed value.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
@example
consta=signal(1);
constb=signal(2);
constsum=computed(() =>a() +b());
sum(); // β 3
a(10);
sum(); // β 12 (re-evaluated lazily)
computed(() =>
constcount: () =>number (+1overload)
count() *2);
exportdefaultfunction
functionCounter():JSX.Element
Counter() {
const
constvalue:number
value=
useSignal<number>(value: () => number): number
Subscribe to any readable signal β writable or computed β returning its current value.
Accepts any zero-argument callable () => T, which includes both Signal<T> and
Computed<T>. Using () => T instead of Computed<T> prevents TypeScript from
picking the write overload of Signal<T> during type inference.
@template β T - The type of the signal value.
@param β value - A writable Signal<T> or a derived Computed<T>.
@returns β The current value, updated on every signal change.
Subscribe to any readable signal β writable or computed β returning its current value.
Accepts any zero-argument callable () => T, which includes both Signal<T> and
Computed<T>. Using () => T instead of Computed<T> prevents TypeScript from
picking the write overload of Signal<T> during type inference.
@template β T - The type of the signal value.
@param β value - A writable Signal<T> or a derived Computed<T>.
@returns β The current value, updated on every signal change.
Create a signal effect scope tied to a React component's lifetime.
All effects registered inside callback are grouped into a single scope. The scope β and every effect within it β is automatically stopped when the component unmounts.
If your callback returns a Computed<T> signal, the hook will always return its current value, updating reactively as dependencies change. If your callback returns void, the value will be undefined.
Returns the current value of the computed signal (or undefined).
Use this when you want to create multiple related effects at once without individually managing each one's lifecycle. All effects and cleanups inside the callback are automatically cleaned up on unmount.
@template β T - The type of the computed value (if any).
@param β callback - A function that registers one or more signal effects, optionally returning a Computed<T>.
@returns β value β the current value of the computed signal (or undefined).
Creates a reactive side-effect that runs immediately and re-runs whenever
any signal or computed it read during its last execution changes.
Use
onCleanup
inside fn to register teardown logic that runs
before each re-execution and on final disposal.
If effect is called inside an effectScope or another effect, the
new effect is automatically owned by the outer scope and will be disposed
when the scope is disposed.
@param β fn - The side-effect body. Reactive reads inside this function
establish dependency links.
@returns β A disposal function. Call it to stop the effect and run any
registered cleanup.
@example
consturl=signal('/api/data');
conststop=effect(() => {
constcontroller=newAbortController();
fetch(url(), { signal: controller.signal });
onCleanup(() => controller.abort());
});
url('/api/other'); // previous fetch is aborted, new one starts
stop(); // final cleanup: abort the last fetch
effect(() =>
var console:Console
console.
Console.log(...data: any[]): void
The console.log() static method outputs a message to the console.