# Philosophy

ElementsKit is a library of reactive primitives, not a framework. Each piece is its own import, runs on its own, and composes with the others — inside React, inside a custom element, or on its own in a script.

## Principles

Four threads run through every API choice:

**Compose, don't configure**

Small focused APIs — `signal`, `computed`, `on`, `fromEvent`, `async`. Combine primitives instead of maintaining an overloaded interface.

**Close to the platform**

Thin or absent abstraction — no virtual DOM, no proxies, no build steps. JSX is `createElement`, custom elements *are* `HTMLElement`.

**Predictable and explicit**

`signal` / `compose` are reactive; nothing else is. No heuristic dependency tracking, no hidden subscriptions.

**Designed for the AI age**

Primitives compose into higher-level blocks. Swap one block at a time instead of maintaining long lines of code.

### Compose, don't configure

ElementsKit gives you primitives you combine, not a system you configure. No config object, no plugin tree, no convention to memorize — the call site is the contract. Need a query layer? Compose `async` + `retry` + storage — swap the fetcher; leave the retry and storage alone. Need browser-API hooks? Import the utility. The surface grows by adding primitives, not by stacking flags on an existing one — overloaded interfaces accumulate breaking changes and deprecations that every consumer has to track and migrate through.



The table below maps ElementsKit equivalents to each major feature from the usual stack — a **UI framework** (React, Vue, Solid), a **custom-element** library (Lit), a **server-state** library (TanStack Query, SWR), a **browser-hooks** pack (react-use, vueuse, solid-primitives), and a **state** manager (Zustand, Jotai, MobX).

| Layer               | Feature               | ElementsKit                                        |
| ------------------- | --------------------- | -------------------------------------------------- |
| **State**           | Shared reactive state | `class Store { @reactive() count = 0 }`            |
|                     | Derived value         | `computed(() => store.count * 2)`                  |
| **Server state**    | Query                 | `async(() => fetch(url())).start()`                |
|                     | Mutation              | `mutation.run({ id, name })`                       |
|                     | Retry with backoff    | `async(retry(fn, 5, n => 2**n * 100)).start()`     |
|                     | Persist result        | `async(fetcher.then(v => store(v))).start()`       |
| **Browser hooks**   | Online / offline      | `online()`                                         |
|                     | Viewport size         | `windowSize.width()`                               |
|                     | Media query           | `createMediaQuery("(prefers-color-scheme: dark)")` |
|                     | Debounce              | `createInterval(300)`                              |
| **UI framework**    | Mount into DOM        | `render(el, () => <App/>)`                         |
|                     | Function component    | `function Counter(props) { return <div/> }`        |
|                     | Class component       | `class Counter { render() { return <div/> } }`     |
|                     | Lifecycle cleanup     | `onCleanup(() => …)`                               |
| **Custom elements** | Reactive HTMLElement  | `@attributes class X extends HTMLElement {}`       |
|                     | Reactive property     | `@reactive() count = 0`                            |
|                     | Attribute handler     | `static [attr] = { name(this, v) {} }`             |
|                     | Slot                  | `children = new Slot()`                            |

### Close to the platform

Heavy abstraction over native APIs is where beginners get burned — they learn the abstraction, not the platform underneath, and the leak surfaces in production. ElementsKit keeps the layer thin or absent. JSX compiles to `document.createElement`. `promise` *extends* the native `Promise` (still awaitable, still chainable). `async` instances are thenable. Custom elements *are* `HTMLElement`.

| ElementsKit          | Native API               | Relationship                                     |
| -------------------- | ------------------------ | ------------------------------------------------ |
| JSX                  | `document.createElement` | Compiles to                                      |
| `promise`            | `Promise`                | Extends (still awaitable, still chainable)       |
| `async`              | `Promise`                | Thenable — `await asyncInstance` works           |
| Custom element       | `HTMLElement`            | Extends — registered via `customElements.define` |
| `@reactive` field    | Plain class field        | Decorator — no proxy, no property rewrite        |
| `on(el, "click", …)` | `addEventListener`       | Thin wrapper returning `Disposable`              |

### Predictable and explicit

No magic. `signal` / `compose` are reactive; nothing else is. No heuristic dependency tracking, no hidden subscriptions. Derivations live in `computed(...)`, cleanup in `onCleanup`. Stores are plain classes with `@reactive` field decorators.

```tsx
// Framework — magic. Reactive tracking is implicit;
// dep arrays, reducer indirection, StrictMode double-fires.
function Counter() {
  const [s, dispatch] = useReducer((s, a) => {
    switch (a.type) {
      case "inc": return { n: s.n + 1 };
    }
  }, { n: 0 });

  const doubled = useMemo(() => s.n * 2, [s.n]);

  useEffect(() => {
    const id = setInterval(() => dispatch({ type: "inc" }), 1000);
    return () => clearInterval(id);
  }, []);

  return <div>{s.n} / {doubled}</div>;
}
```

### Designed for the AI age

Writing code is cheap — an agent will draft most of it. *Maintaining* it is the bottleneck. A codebase is read and rewritten long after it's first authored, often by an agent rebuilding context from scratch.

ElementsKit is built from primitives that compose into higher-level blocks. Each unit stays testable in isolation, which lets you — or an agent — swap one block at a time without touching the rest.

## Framework integration

Any signal can drive a UI-framework component. The hooks are the bridge, not the primitive — the same utilities work inside React today, Solid or Vue when those integrations ship.

Two hooks bridge signals into React: **`useSignal`** subscribes a component to any signal or computed; **`useScope`** ties `effect`, `onCleanup`, and factory utilities to the component's lifetime for automatic disposal. See [React integration](/integrations/react) for the full treatment.

```tsx
// effect + onCleanup inside useScope — torn down with the component.
/** @jsxImportSource react */
import { effect, onCleanup } from "elements-kit/signals";
import { useScope } from "elements-kit/integrations/react";
import { online } from "elements-kit/utilities/network";

export function NetworkLogger() {
  const offline = useScope(() => {
    effect(() => {
      console.log("online:", online());
    });

    const id = setInterval(() => console.log("tick"), 1000);
    onCleanup(() => clearInterval(id));
    return computed(() => !online());
  });

  return <p>Watching network in the console.</p>;
}
```

## Architecture

The pieces are independent and composable — not a strict hierarchy. Signals are the shared language that connects everything.



## Pattern guide

| Pattern               | When to use it                                                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Plain signals         | Pure logic, no UI. Node scripts, workers, utilities that need reactive state.                                                                                                         |
| Elements / JSX        | Inline reactive UI in scripts. Quick components appended directly to the DOM.                                                                                                         |
| Components            | Class-based UI with `render()`. Share state across multiple elements via stores.                                                                                                      |
| Custom elements       | Reusable across frameworks. Native HTML integration, Shadow DOM, framework-agnostic distribution.                                                                                     |
| Framework integration | Drop a signal, store, or utility into an existing React (today; Solid/Vue planned) app via `useSignal` / `useScope`. Share reactive state with code that lives outside the framework. |

Start with the simplest option that fits — you can always combine patterns.

## Not for

> **Reach for something else if you need**
>
> ElementsKit is deliberately narrow.
>
> * **Server-side rendering or streaming HTML.** No SSR, no hydration, no React Server Components. Rendering happens in the browser.
> * **A complete app framework.** No router, no data layer, no file-system conventions — bring or compose your own.
> * **A pre-built component library.** This ships primitives, not components. A design system on top has to come from you or a third party.

Narrow doesn't mean incomplete — signals + elements + custom elements cover the full client-side reactive-UI surface.

## See also

* [Installation](/getting-started/installation)
* [Quick start](/getting-started/quick-start)
* [Signals](/signals)
* [Elements](/elements)
