Primitives
Reactive building blocks for data, forms, routing, context, DOM events, observers, browser state and timing. One import each, any framework.
Primitives are small factories that return signals. Each one is its own import, needs no framework, and cleans up with the scope it was created in. Read them in plain scripts, custom elements, JSX, or React.
import { effect } from "elements-kit/signals";
import { online } from "elements-kit/utilities/network";
import { createMediaQuery } from "elements-kit/utilities/media-query";
const dark = createMediaQuery("(prefers-color-scheme: dark)");
effect(() => {
document.title = `${online() ? "Online" : "Offline"} · ${dark() ? "dark" : "light"}`;
});Data
promise, async, retry, storage. Queries, mutations and persistence without a query library.
Forms
FormObject reads and writes a form as a nested object.
Routing
Location, search params, URL patterns and navigate: a router from signals.
Context
setContext / getContext across JSX, custom elements and scopes.
DOM events
on, hover, focus-within, click-outside, long-press.
Observers
Element size and scroll, resize, intersection and mutation observers.
Browser state
Media queries, window size, orientation, online status, devices.
Timing
Timeouts, intervals, debounced, throttled and previous values.
Media
Audio and video player state as signals.
Building primitives
fromEvent and sync: turn any event API into a signal.
Catalog
Every primitive imports from elements-kit/utilities/<module>. Universal primitives run anywhere; browser ones need a DOM and return neutral defaults on the server.
| Primitive | Module | Returns | Runs in |
|---|---|---|---|
| Data | |||
promise | promise | ComputedPromise<T> | Universal |
async | async | Async<T> | Universal |
retry | retry | () => Promise<T> | Universal |
createLocalStorage | storage | Signal<T> | Browser |
createSessionStorage | storage | Signal<T> | Browser |
| Forms | |||
FormObject | form-object | { toObject, fromObject, clear } | Browser |
| Routing | |||
currentLocation | location | { hash, href, pathname, search } | Browser |
createSearchParam | search-params | Computed<string | null> | Browser |
navigate | routing | void | Browser |
matches / match | routing | Computed<boolean> / Computed<URLPatternResult | null> | Browser |
isLocalNavigationEvent | routing | boolean | Browser |
| Context | |||
setContext / getContext | context | void / T | undefined | Universal |
| DOM events | |||
on | event-listener | () => void | Browser |
createHover | hover | Computed<boolean> | Browser |
createFocusWithin | focus-within | Computed<boolean> | Browser |
onClickOutside | on-click-outside | () => void | Browser |
createLongPress | long-press | () => void | Browser |
activeElement | active-element | Computed<Element | null> | Browser |
| Observers | |||
createElementRect | element-rect | ElementRect | Browser |
createElementScroll | element-scroll | { x, y } & Disposable | Browser |
createResizeObserver | resize-observer | Disposable | Browser |
createIntersectionObserver | intersection-observer | Disposable | Browser |
createMutationObserver | mutation-observer | Disposable | Browser |
| Browser state | |||
createMediaQuery | media-query | Computed<boolean> | Browser |
windowSize | window-size | { width, height } | Browser |
orientation | orientation | Computed<…> | Browser |
online | network | Computed<boolean> | Browser |
windowFocused | window-focus | Computed<boolean> | Browser |
createMediaDevices | media-devices | Computed<MediaDeviceInfo[]> | Browser |
| Timing | |||
createTimeout | timeout | { pending, start, stop, reset } | Universal |
createInterval | interval | { pending, start, stop, reset } | Universal |
createDebounced | debounced | Computed<T> | Universal |
createThrottled | throttled | Computed<T> | Universal |
createPrevious | previous | Computed<T | undefined> | Universal |
| Media | |||
createMediaPlayer | media-player | { playing, muted, volume, … } | Browser |
| Building primitives | |||
fromEvent | event-driven | Subscribe | Universal |
sync | event-driven | [Computed<T> | Signal<T>, () => void] | Universal |
Use them anywhere
Utilities return plain signals and computeds. useSignal from elements-kit/integrations/react connects them to React components with no special glue.
Singleton utilities are module-level values shared across the whole app. Pass them directly to useSignal:
import { useSignal } from "elements-kit/integrations/react";
import { windowSize } from "elements-kit/utilities/window-size";
import { currentLocation } from "elements-kit/utilities/location";
function Layout() {
const width = useSignal(windowSize.width);
const path = useSignal(currentLocation.pathname);
return <p>{path}: {width}px wide</p>;
}Factory utilities with per-component lifetime go inside useScope so they are created and cleaned up with the component:
import { useSignal, useScope } from "elements-kit/integrations/react";
import { createDebounced } from "elements-kit/utilities/debounced";
import { signal } from "elements-kit/signals";
const query = signal("");
function Search() {
const debounced = useScope(() => createDebounced(query, 300));
return (
<>
<input onInput={(e) => query(e.currentTarget.value)} />
<p>Searching for: {debounced}</p>
</>
);
}Writable signals from storage or scroll work as both getter (via useSignal) and setter (call directly):
import { useSignal } from "elements-kit/integrations/react";
import { createLocalStorage } from "elements-kit/utilities/storage";
const theme = createLocalStorage("theme", "light");
function ThemeToggle() {
const current = useSignal(theme);
return (
<button onClick={() => theme(current === "light" ? "dark" : "light")}>
{current}
</button>
);
}See also
- Signals: the core every primitive returns
- Scopes & cleanup: how primitives release listeners and timers
- Integrations: React, Vue, Svelte and more