Signals are the reactive core of ElementsKit. Every UI update, derived value, and side effect is driven by three primitives: signal, computed, and effect.
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("Alice");
constname:Updater<string> &Computed<string>
constname: () =>string (+1overload)
name(); // "Alice"
constname: (value:string) =>void (+1overload)
name("Bob"); // write
constname: () =>string (+1overload)
name(); // "Bob"
Signals are synchronous β every write immediately notifies dependents.
computed
A computed derives a value from one or more signals. It is lazy β only re-evaluates when a dependency has changed and the value is read again. Results are cached between reads.
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.
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.
A computed is read-only β you can only call it with no arguments.
effect
An effect runs a side effect whenever its signal dependencies change. It runs immediately on creation to collect dependencies, then re-runs on every change. Returns a stop function.
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,
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
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("light");
conststop=
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(() => {
conststop: () =>void
// Reads theme β becomes a dependency automatically
var document:Document
window.document returns a reference to the document contained in the window.
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,
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,
functioneffectScope(fn: () =>void): () =>void
Creates an ownership scope that groups reactive effects so they can all be
disposed at once.
Effects and nested scopes created inside fn are linked to this scope.
When the returned disposal function is called, all owned effects are stopped
in cascade β triggering their registered
onCleanup
callbacks β and
the scope itself is removed from any parent scope that owns it.
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("/home");
// All effects inside are grouped together
conststop=
functioneffectScope(fn: () =>void): () =>void
Creates an ownership scope that groups reactive effects so they can all be
disposed at once.
Effects and nested scopes created inside fn are linked to this scope.
When the returned disposal function is called, all owned effects are stopped
in cascade β triggering their registered
onCleanup
callbacks β and
the scope itself is removed from any parent scope that owns it.
@returns β A disposal function that tears down all owned effects and the scope
itself.
@example
conststopAll=effectScope(() => {
effect(() => console.log('a:', a()));
effect(() => console.log('b:', b()));
});
stopAll(); // both effects stopped simultaneously
effectScope(() => {
conststop: () =>void
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(() =>
var console:Console
console.
Console.log(...data: any[]): void
The console.log() static method outputs a message to the console.
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.
Use effectScope when you want to start and stop a group of related effects together β e.g. tied to a componentβs lifetime.
onCleanup
Registers a cleanup callback inside the currently running effect or effectScope. The callback runs before the effect re-runs and when the effect is disposed.
// Runs before the next fetch (url changed) or on stop()
onCleanup(() => controller.abort());
});
url("/api/other"); // previous fetch aborted, new one starts
onCleanup works at any call depth inside the effect β you can call it anywhere without passing anything down.
You can use onCleanup inside a computed getter to manage resources that need cleanup (like timers or subscriptions). The cleanup runs before the computed re-evaluates (when dependencies change), and also when the computed loses its last subscriber (to prevent resource leaks).
This ensures that any resources created inside the computed are always cleaned up at the right time.
batch
Defers all signal notifications until the batch completes. Use it to group multiple writes into a single update pass β avoids intermediate re-renders.
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.
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()
Counter.count: number
count=0;
Counter.doubled: () => number
doubled=computed(() =>this.
Counter.count: number
count*2);
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)
}
const
constc:Counter
c=new
constructor Counter(): Counter
Counter();
constc:Counter
c.
Counter.count: number
count=5;
constc:Counter
c.
Counter.doubled: () => number
doubled(); // β 10
@reactive is equivalent to a private signal + get/set accessor β it just removes the boilerplate. Works with computed too: pass the signal source to make a field read-only.