Skip to content

Phaze Signals

Phaze’s only state primitive is signal(). The rest of state management — context replacement, store grouping, derived state — composes out of where you declare it and what reactive primitives observe it. This page covers the primitive itself: its naming history across the signals lineage, and the one decision you make when using it.

What each lineage member actually calls it

Section titled “What each lineage member actually calls it”

Fifteen years of signals; no agreed term for “the non-local kind.” Every library has its own shorthand:

LibraryTerm for “non-local” signalMechanism
Knockout (2010)No special term — observables declared anywhereModule-level just by virtue of where you put the JS
S.js (2014)Same — no formal termSame
MobX (2015)“Observable state” (general), “store” (when class-grouped)Class instance, often exported as singleton
Vue 3 (2020)“Composable” (when wrapped in a function); “ref” otherwiseref() declared at module level + exported, or in a composable
SolidJS (2018)“Global state” (colloquial), “shared state” (docs) — no formal termModule-level createSignal(), exported
@preact/signals (2022)“Shared state” / “global signal” (colloquially)Module-level signal(), exported
Angular (2023)“Service” — the DI container is the unit, not the signalServices hold signals; sharing via DI
Svelte 5 runes (2024)“Shared state” / “module-level rune”$state at module top, exported
Jotai”atom”atom() is the unit; module-level declaration
Recoil”atom”Same
Zustand”store”create() returns a store hook
TC39 Signals proposalJust “Signal” — no formal qualifierModule-level convention assumed

Across the lineage, what’s converged:

  • signal as the universal primitive name
  • Vague qualifiers — “global,” “shared,” “module-level,” “module-scoped” — used interchangeably in articles, often within the same article
  • Specific renamesatom (Jotai), store (Zustand) — when libraries want their own marketing/branding

No ratified term exists for “a signal that’s not declared inside a component.” This isn’t an oversight — it’s a vocabulary gap.

The only decision you make.

import { signal } from '@madenowhere/phaze'
// Module scope — outlives any component, importable anywhere
export const theme = signal<'light' | 'dark'>('light')
function Counter() {
// Component scope — disposed when Counter's scope disposes
const count = signal(0)
return <button onClick={() => count.update(n => n + 1)}>{count}</button>
}

That’s the entire decision tree. The mechanism is identical in both cases — what changes is where you write the line, and the JavaScript module / function scope handles the rest.

One decision: where you declare it

Where you declare itLifetimeWhat it replaces in React
Inside the component functionDisposed with the component scopeuseState, useReducer
At module top levelLives until page navigationuseContext, Redux, Zustand, Jotai, every store library
At module level, in a dedicated store.tsSame, but groupedA <StateProvider> tree + useStoreSelector machinery