This is the full developer documentation for Valdres — reactive state management for React, Vue, Svelte, Solid, and Angular. Shared (core/plugin) pages are included once; they exist per framework at /react/…, /vue/…, /svelte/…, /solid/…, /angular/… with the examples adapted. Source: https://valdres.dev/react/atom.md # atom Signature `atom(defaultValue: T, options?: AtomOptions): Atom` valdres Creates a reactive piece of state An atom holds a single value that can be read and written from any component or outside of React entirely. ## Usage ```ts import { atom } from "valdres" // Simple atom with a default value const countAtom = atom(0) // Atom with an async initializer const userAtom = atom(() => fetch("/api/user").then(res => res.json()) ) // With a name for debugging and error messages const nameAtom = atom("default", { name: "nameAtom" }) ``` ## Parameters | Parameter | Type | Description | | ------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `defaultValue` | `T \| () => T \| Promise` | Initial value, or a function that returns one | | `options.name` | `string` | Optional name for debugging, devtools, and validation error messages | | `options.schema` | `Schema` | Schema for runtime validation and type inference. A no-op unless validation is enabled. See [Schema Validation](https://valdres.dev/guides/schema-validation). | | `options.schemaValidation` | `boolean` | Per-atom override of the store's `schemaValidation` flag — `true` always validates this atom, `false` exempts it | | `options.mutable` | `boolean` | Opt out of development/test deep-freezing. Required for mutable built-ins and host objects that cannot be frozen safely. | | `options.maxAge` | `Reactive` | Re-fetch interval in milliseconds (requires async default). Accepts a number, atom, or selector. | | `options.staleWhileRevalidate` | `Reactive` | Serve stale data for this many ms while re-fetching. Accepts a number, atom, or selector. | | `options.staleIfError` | `Reactive` | Serve stale data for this many ms after re-fetch errors. Accepts a number, atom, or selector. | ## Immutability and exotic values In development mode, Valdres deep-freezes immutable values on every accepted write so accidental in-place changes fail immediately. This includes plain object and array atom values; Error objects and Promise handles remain usable while their object surface is frozen. Production builds skip this development check. In runtimes with `process.env`, `NODE_ENV` selects the mode. Process-less CDN and edge runtimes default to production. To select the process-less debugging entry and retain development-only deep-freeze, validation diagnostics, warnings, and instrumentation, enable the package's `development` export condition in the bundler consistently across the entire application, including framework adapters. Do not mix default and development entrypoints: they intentionally use isolated runtime graphs. No-build CDN users can request the published `dist/development/index.js` file directly. That raw entry must not be combined with an adapter resolved from the default package entry; adapter applications should use the bundler condition so both the public and adapter-internals entrypoints select the development graph. Bun's bundler enables the `development` export condition by default. Use `bun build --production` when building a production process-less or edge app; adding only `--conditions=production` does not disable Bun's development condition. `Object.freeze` cannot make mutable built-ins or host objects reliably immutable. This includes `Map`, `Set`, `WeakMap`, `WeakSet`, `Date`, `ArrayBuffer`, `SharedArrayBuffer`, `DataView`, typed arrays, browser API objects, and other branded exotic values. Valdres rejects such values with an actionable error instead of storing something that only appears frozen. Ordinary class instances have their own property graph frozen, like plain objects. Store an immutable plain-data representation when possible, or opt out explicitly: ```ts const bytesAtom = atom(new Uint8Array(), { mutable: true }) ``` With `mutable: true`, Valdres does not protect the value from in-place changes. Use replacement updates (or an appropriate custom `equal` function) so writes still produce notifications. The default equality function compares binary buffers and views by their visible bytes. It compares every own enumerable property, including symbol-keyed ones and properties set alongside the contents of an array, `Map`, or `Set`, so a write that changes only one of those is still a change. A custom `valueOf` or `toString` narrows the comparison but never replaces it — two values that stringify alike but hold different properties are different values. Binary buffers and views are the one exception: they are compared by their bytes alone. Enumerating the keys of a typed array costs time proportional to its length, so properties attached beside the bytes are not compared — keep such metadata in a separate atom. ## Async atoms > **Tip** > > > When you pass a function as the default value, it becomes an async atom. The function is called lazily — only when the atom is first read. In React, async atoms work seamlessly with `Suspense`: ```ts const dataAtom = atom(async () => { const res = await fetch("/api/data") return res.json() }) ``` ## Caching & revalidation Async atoms support built-in stale-while-revalidate caching via `maxAge`, `staleWhileRevalidate`, and `staleIfError`. This is useful for data that should periodically refresh from an API while keeping the UI responsive. ### maxAge Sets how often the atom re-fetches its data (in milliseconds). Revalidation only happens while the atom has active subscribers. ```ts const pricesAtom = atom( async () => { const res = await fetch("/api/prices") return res.json() }, { maxAge: 30_000 }, // Re-fetch every 30 seconds ) ``` When `maxAge` expires, the atom calls its default function again. Without `staleWhileRevalidate`, the atom immediately enters a loading state (returns a pending promise), which triggers `Suspense` in React. ### staleWhileRevalidate Keeps serving the previous value while the re-fetch is in progress, instead of showing a loading state. The value updates seamlessly once the new data resolves. ```ts const pricesAtom = atom( async () => { const res = await fetch("/api/prices") return res.json() }, { maxAge: 30_000, // Re-fetch every 30s staleWhileRevalidate: 60_000, // Serve stale data for up to 60s while fetching }, ) ``` This means your UI never shows a loading spinner on re-fetches — users see the previous data until the fresh data arrives. ### staleIfError Extends the stale window when re-fetches fail. Instead of surfacing an error to the UI, the atom keeps serving the last successful value for the specified duration. ```ts const pricesAtom = atom( async () => { const res = await fetch("/api/prices") if (!res.ok) throw new Error("API error") return res.json() }, { maxAge: 30_000, staleWhileRevalidate: 60_000, staleIfError: 300_000, // Keep stale data for up to 5 minutes on errors }, ) ``` If the re-fetch fails within the `staleIfError` window (measured from the last successful fetch), the previous value is preserved. Once the window expires, the rejected promise is surfaced so error boundaries can handle it. ### Example: API response with caching ```ts import { atom } from "valdres" type User = { id: string; name: string; email: string } const currentUserAtom = atom( async () => { const res = await fetch("/api/me", { headers: { Authorization: `Bearer ${getToken()}` }, }) if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) return res.json() }, { maxAge: 60_000, // Refresh every minute staleWhileRevalidate: 120_000, // Show stale for 2 min while fetching staleIfError: 300_000, // Tolerate errors for 5 min }, ) ``` **Timeline of behavior:** | Time | Event | What the UI sees | | ------- | ----------------------------------- | ----------------------------------- | | 0s | First read, fetch starts | Suspense loading state | | \~200ms | Fetch resolves | User data | | 60s | `maxAge` expires, re-fetch starts | Previous user data (SWR) | | \~60.2s | Re-fetch resolves | Updated user data | | 120s | `maxAge` expires, re-fetch fails | Previous user data (`staleIfError`) | | 180s | `maxAge` expires, re-fetch succeeds | Fresh user data | > **Note** > > > Revalidation is only active while the atom has subscribers. When all components unsubscribe, the interval is cleared. The next subscription triggers a fresh fetch. ### Reactive cache configuration Cache options can be dynamic by passing an atom or selector instead of a static number. When the config value changes, the revalidation interval automatically restarts with the new timing. ```ts import { atom, selector } from "valdres" const refreshIntervalAtom = atom(30_000) const pricesAtom = atom( async () => { const res = await fetch("/api/prices") return res.json() }, { maxAge: refreshIntervalAtom }, ) // Changing the interval updates the revalidation schedule store.set(refreshIntervalAtom, 5_000) // now refreshes every 5 seconds ``` You can also use a selector to derive the interval from other state: ```ts const realtimeModeAtom = atom(false) const maxAgeSelector = selector(get => get(realtimeModeAtom) ? 1_000 : 30_000 ) const dataAtom = atom( async () => fetch("/api/data").then(r => r.json()), { maxAge: maxAgeSelector }, ) ``` ### cacheMeta Use `cacheMeta()` to get a reactive selector that exposes the caching state of an atom. This is useful for showing loading indicators during revalidation or displaying when data was last refreshed. ```ts import { atom, cacheMeta } from "valdres" const dataAtom = atom( async () => fetch("/api/data").then(r => r.json()), { maxAge: 30_000, staleWhileRevalidate: 60_000 }, ) const meta = store.get(cacheMeta(dataAtom)) // { // isRevalidating: false, // lastSuccessAt: 1713100800000, // maxAge: 30000, // staleWhileRevalidate: 60000, // staleIfError: undefined, // } ``` `cacheMeta()` returns a selector, so you can subscribe to it reactively: ```ts const dataMeta = cacheMeta(dataAtom) store.sub(dataMeta, () => { const meta = store.get(dataMeta) console.log(meta?.isRevalidating) // true when re-fetching console.log(meta?.lastSuccessAt) // timestamp of last success }) ``` Returns `null` for atoms that don't have `maxAge` configured. ## Schema validation Pass a `schema` — any [Standard Schema](https://standard-schema.dev) (Zod, Valibot, ArkType, …) or `parse()`-style validator — to validate the atom's values at runtime and infer its type without a generic: ```ts import { atom, store } from "valdres" import { z } from "zod" // Typed Atom from the schema — no generic needed const nameAtom = atom(undefined, { name: "nameAtom", schema: z.string() }) // Validation is opt-in per store (off by default) const s = store({ schemaValidation: true }) s.set(nameAtom, 42) // throws SchemaValidationError naming 'nameAtom' ``` Validation is validate-only (the original value is stored unchanged) and can be overridden per atom via `schemaValidation: true | false`. See the [Schema Validation guide](https://valdres.dev/guides/schema-validation) for error handling, async behavior, and limitations. ## See also - [globalAtom](https://valdres.dev/valdres/globalAtom) — share a single atom's value across every store - [atomFamily](https://valdres.dev/valdres/atomFamily) — create a collection of atoms keyed by a parameter - [selector](https://valdres.dev/valdres/selector) — derive computed state from atoms - [Schema Validation](https://valdres.dev/guides/schema-validation) — runtime validation and type inference - [useAtom](https://valdres.dev/react/useAtom) — read and write an atom in your components --- Source: https://valdres.dev/react/atomFamily.md # atomFamily Signature `atomFamily(defaultFactory?: (key: K) => T, options?: AtomFamilyOptions): AtomFamily` valdres Create a keyed collection of atoms Creates a family of atoms keyed by a parameter. Useful for collections of entities like users, todos, or items where each instance needs its own piece of state. ## Usage ```ts import { atomFamily } from "valdres" // Simple family with no default const todoAtom = atomFamily() store.set(todoAtom("abc-123"), { title: "Buy milk", done: false }) store.get(todoAtom("abc-123")) // { title: "Buy milk", done: false } ``` ## With a default value factory Pass a function that receives the key and returns the default value: ```ts const userAtom = atomFamily(id => ({ id, name: "", email: "", })) // The default is generated when first accessed store.get(userAtom("user-1")) // { id: "user-1", name: "", email: "" } ``` ## Global families Use [`globalAtomFamily`](https://valdres.dev/valdres/globalAtomFamily) to share each member's value across stores. `options.name` is required — a global family is addressed by it, so omitting it is a type error and remains a runtime error for untyped JavaScript. ```ts import { globalAtomFamily } from "valdres" const featureFlagAtom = globalAtomFamily((key: string) => false, { name: "app/feature-flags", }) ``` Re-defining a family under the same name returns the existing family instead of creating a second one. Selectors do not have a global counterpart. ## Key identity Family calls are keyed by a deterministic structural encoding. Primitive types, argument count, Arrays, plain Objects, Dates, Maps, and Sets remain distinct; object property order and Map/Set insertion order do not affect identity. `BigInt` is supported too. Values without deterministic structural semantics—such as Symbols, functions, Promises, class instances, accessor properties, and cyclic structures—throw a `TypeError`. Use `keyOf` to derive a supported identity for those arguments, or to intentionally group multiple arguments: ```ts type Entity = { id: string; self?: Entity } const entityAtom = atomFamily(entity => entity, { keyOf: entity => entity.id, }) ``` `keyOf` receives the same argument tuple as the family factory. Its result is run through the same canonical codec. > **Transferred families need JSON-safe args** > > > Key identity is wider than JSON on purpose — it is a local, in-process > concern. But `dehydrate` emits family args raw (schemas encode _values_, not > keys) and `hydrate` re-derives each member with `family(...args)` from the > parsed payload. A `Date`, `Map`, `Set`, `BigInt`, `NaN` or `undefined` > argument does not survive that round-trip, so the value would land on a > phantom member while the real one keeps its default. Dev builds throw from > `dehydrate`, naming the family and the argument path. Key transferred > families by strings and numbers (`user(id)`, `day(date.toISOString())`) — > `keyOf` does not help here, since the raw args are what crosses the wire. > > For the same reason, a named family's `keyOf` must derive its key from the > arguments' JSON **data** (`entity => entity.id`). JSON carries no property > descriptors, no frozen/sealed flag, and no object identity — two references > to one object arrive as two objects — so a `keyOf` reading any of those > returns a different key after hydration. `dehydrate` cannot detect this and > does not try. The legacy `release(...args)` method is deprecated and has no effect. Family members leave the weak identity cache automatically once no caller or store can reach them. Manual eviction could otherwise create two live member objects for the same arguments and split their values across stores. ## Subscribing to a family > **Tip** > > > Subscribe to an entire family to be notified when any member changes. The callback receives that member's family arguments. ```ts store.sub(todoAtom, id => { console.log("Changed todo ID:", id) }) ``` ## In React ```tsx import { useValue } from "valdres-react" function UserProfile({ userId }) { const user = useValue(userAtom(userId)) return
{user.name}
} ``` ## See also - [atom](https://valdres.dev/valdres/atom) — create a single atom - [globalAtomFamily](https://valdres.dev/valdres/globalAtomFamily) — share each member's value across every store - [selectorFamily](https://valdres.dev/valdres/selectorFamily) — derive state from a family of atoms - [index](https://valdres.dev/valdres/index) — reactively filter family members by a term --- Source: https://valdres.dev/react/globalAtom.md # globalAtom Signature `globalAtom(defaultValue: T, options: GlobalAtomOptions): GlobalAtom` valdres Creates a cross-store singleton atom A global atom is a single shared value that stays in sync across every store that touches it — writing in one store is immediately visible from any other. This is the mechanism behind Valdres' `@valdres/*` browser-API packages: each wraps one global atom around a browser subscription, so any store in the app sees the same `online` status, `geolocation` position, and so on. `options.name` is required — it is the atom's global address. Re-using a name that's already registered throws. ## Usage ```ts import { globalAtom } from "valdres" const onlineAtom = globalAtom(navigator.onLine, { name: "app/online" }) ``` Same call shape as [`atom`](https://valdres.dev/valdres/atom) — the only difference is that `options.name` is required instead of optional. ## The self-accessors A global atom carries three extra methods beyond the ordinary atom surface, for reading and writing it without a `Store` handle: ```ts onlineAtom.getSelf() // read the current value onlineAtom.setSelf(false) // write — fans out to every store onlineAtom.resetSelf() // restore the default value everywhere ``` These are what a browser-API package's `onMount` uses to push external events (a `navigator.onLine` change, a geolocation update, …) into the shared value: ```ts const onlineAtom = globalAtom(navigator.onLine, { name: "app/online", onMount: () => { const update = () => onlineAtom.setSelf(navigator.onLine) window.addEventListener("online", update) window.addEventListener("offline", update) return () => { window.removeEventListener("online", update) window.removeEventListener("offline", update) } }, }) ``` `onMount` fires once, when the FIRST subscriber across ANY store attaches, and its cleanup fires once, when the LAST subscriber across ALL stores detaches — so the underlying browser subscription is shared, not duplicated per store. ## Parameters `globalAtom` accepts the same options as [`atom`](https://valdres.dev/valdres/atom) (`schema`, `schemaValidation`, `onSet`, `onMount`, `maxAge`, `mutable`, `staleWhileRevalidate`, `staleIfError`) as `GlobalAtomOptions` — identical to `AtomOptions`, except `name` is required instead of optional. ## See also - [atom](https://valdres.dev/valdres/atom) — create a single, per-store atom - [globalAtomFamily](https://valdres.dev/valdres/globalAtomFamily) — a keyed collection of global atoms --- Source: https://valdres.dev/react/globalAtomFamily.md # globalAtomFamily Signature `globalAtomFamily(defaultFactory: (key: K) => T, options: GlobalAtomFamilyOptions): GlobalAtomFamily` valdres Creates a keyed collection of cross-store singleton atoms Combines [`atomFamily`](https://valdres.dev/valdres/atomFamily) and [`globalAtom`](https://valdres.dev/valdres/globalAtom): each member is keyed by a parameter, and each member's value is shared across every store that touches it. `options.name` is required — it is the family's global address. Re-defining a family under the same name returns the existing family instead of creating a second one. The first definition wins: its default and options remain active, and a development build warns that the later definition was ignored. This makes hot-module re-evaluation safe without silently changing live global state. A detectable contract mismatch, such as reusing an atom's name or changing the detectable `keyOf` arity, throws instead. ## Usage ```ts import { globalAtomFamily } from "valdres" const featureFlagAtom = globalAtomFamily((key: string) => false, { name: "app/feature-flags", }) featureFlagAtom("dark-mode").setSelf(true) ``` Same call shape as [`atomFamily`](https://valdres.dev/valdres/atomFamily) — the only difference is that `options.name` is required instead of optional. Each member carries the same `getSelf` / `setSelf` / `resetSelf` accessors as a plain [`globalAtom`](https://valdres.dev/valdres/globalAtom#the-self-accessors): ```ts const flag = featureFlagAtom("dark-mode") flag.getSelf() // read without a Store handle flag.setSelf(true) ``` ## Parameters `globalAtomFamily` accepts the same options as [`atomFamily`](https://valdres.dev/valdres/atomFamily) (`keyOf`, plus every `AtomOptions` field) as `GlobalAtomFamilyOptions` — identical to `AtomFamilyOptions`, except `name` is required instead of optional. ## See also - [globalAtom](https://valdres.dev/valdres/globalAtom) — a single cross-store atom - [atomFamily](https://valdres.dev/valdres/atomFamily) — a per-store keyed collection of atoms --- Source: https://valdres.dev/react/index.md # index Signature `index(family: AtomFamily, predicate: (value: Value, term: Term) => boolean, options?: IndexOptions): (term: Term) => Selector[]>` valdres Create memoized reactive family filters `index` creates a memoized selector for each search term. Each selector returns the atoms in an `atomFamily` whose current values satisfy the predicate. ```ts import { atomFamily, index, store } from "valdres" const post = atomFamily<{ title: string; tags: string[] }, [string]>(null) const postsByTag = index( post, (value, tag: string) => value.tags.includes(tag), { name: "postsByTag" }, ) const app = store() app.set(post("one"), { title: "First post", tags: ["news"] }) app.get(postsByTag("news")) // [post("one")] ``` ## Reactive-filter semantics Despite its name, `index` is a reactive filter rather than a materialized database index. The first read for a term is O(n) in the number of family members. After a member changes, its predicate selector is the only predicate that recomputes. If its boolean result is unchanged, equality pruning stops there in O(1). If the result flips, the term selector walks the family to preserve the ordered array result, so a membership-changing update is O(n). This is a good fit for modest collections and for queries whose predicate is more expensive than walking the family. For large, frequently updated collections that require sublinear writes, maintain a lookup structure in store state as part of the same transaction that changes the source family. Term selectors are cached weakly. Repeated calls return the same selector while a caller or live store dependency graph retains it, but querying many one-off terms does not retain every selector and term for the lifetime of the `index` function. ## Term identity and `keyOf` Terms use the same collision-safe structural key codec as `atomFamily` and `selectorFamily`. Primitive types remain distinct, while plain Object property order and Map/Set insertion order do not affect identity. Symbols, functions, Promises, class instances, accessor properties, and cyclic structures are not supported by default. Use `keyOf` to derive supported identity for one of those terms, or to intentionally group multiple term objects: ```ts type Query = { tag: string; uiState: Map } const postsByQuery = index( post, (value, query: Query) => value.tags.includes(query.tag), { keyOf: query => query.tag }, ) ``` `keyOf` receives the term and its result is encoded by the canonical family-key codec. ## See also - [atomFamily](https://valdres.dev/valdres/atomFamily) — create the source collection - [selectorFamily](https://valdres.dev/valdres/selectorFamily) — create arbitrary keyed derivations - [selector](https://valdres.dev/valdres/selector) — derive a single value --- Source: https://valdres.dev/react/plugins/bandwidth.md # bandwidth Download/upload speed, latency, and jitter from a live measurement, as async global atoms. > **Runs a measurement** > > > Reading a measured atom triggers a download/upload test on first subscribe. Call `invalidateMeasurement()` to discard the result and re-run. ## Install ```bash bun add @valdres/bandwidth ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/bandwidth](https://valdres.dev/react/plugins/bandwidth) ## Usage ```tsx import { Suspense } from "react" import { useValue } from "valdres-react" import { downloadSpeedAtom, latencyAtom } from "@valdres/bandwidth" function Speed() { const download = useValue(downloadSpeedAtom) const latency = useValue(latencyAtom) return {download.toFixed(1)} Mbps · {latency.toFixed(0)} ms } // The measured atoms suspend until the test resolves export function App() { return ( ) } ``` ## Exports | Export | Kind | Type | | ------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `downloadSpeedAtom` | atom (read-only, async) | `number` — Mbps | | `uploadSpeedAtom` | atom (read-only, async) | `number` — Mbps | | `latencyAtom` | atom (read-only, async) | `number` — ms | | `jitterAtom` | atom (read-only, async) | `number` — ms | | `measurementStatusAtom` | atom (settable) | `MeasurementStatus` | | `lastMeasurementAtom` | atom (settable) | `number \| null` — timestamp | | `invalidateOnAtom` | atom (settable) | `GlobalAtom[]` | | `measureBandwidth` | util fn | `(options?: MeasureBandwidthOptions) => Promise` | | `invalidateMeasurement` | util fn | `() => void` | | `MeasurementStatus` | type | `"idle" \| "measuring-latency" \| "measuring-download" \| "measuring-upload" \| "complete" \| "error"` | | `BandwidthResult` | type | `{ downloadMbps, uploadMbps, latencyMs, jitterMs, timestamp: number }` | | `MeasureBandwidthOptions` | type | `{ latencySamples?, maxDurationMs?, minDurationMs?, warmupMs?, startStreams?, maxStreams?, stabilityThreshold?: number; signal?: AbortSignal; fresh?: boolean }` | ## Cross-framework One in-flight measurement is shared across every store and framework. `invalidateMeasurement()` clears the result and, if anything is subscribed, kicks off a fresh run. --- Source: https://valdres.dev/react/plugins/browser-color-scheme.md # browser-color-scheme Reads the user's OS-level `prefers-color-scheme` preference and keeps it in sync via a media-query listener. Exposes the raw value plus boolean selectors. ## Install ```bash bun add @valdres/browser-color-scheme ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-color-scheme](https://valdres.dev/react/plugins/browser-color-scheme) ## Usage ```tsx import { useValue } from "valdres-react" import { colorSchemeAtom, isDarkSelector } from "@valdres/browser-color-scheme" function Theme() { const scheme = useValue(colorSchemeAtom) // "dark" | "light" const isDark = useValue(isDarkSelector) return {scheme} } ``` ## Exports | Export | Kind | Type | | ----------------- | ---------------- | ------------------- | | `colorSchemeAtom` | atom (read-only) | `"dark" \| "light"` | | `isDarkSelector` | selector | `boolean` | | `isLightSelector` | selector | `boolean` | ## Cross-framework A global atom plus derived selectors — works in every framework. For full theme management (system preference + user override + persistence), use `@valdres/color-mode`, which builds on top of this. --- Source: https://valdres.dev/react/plugins/browser-contrast.md # browser-contrast Wraps the `prefers-contrast` media query as a global atom (`"no-preference" | "more" | "less" | "custom"`), plus boolean selectors for the `more`/`less` cases. ## Install ```bash bun add @valdres/browser-contrast ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-contrast](https://valdres.dev/react/plugins/browser-contrast) ## Usage ```tsx import { useValue } from "valdres-react" import { contrastAtom, prefersMoreContrastSelector } from "@valdres/browser-contrast" function ContrastBadge() { const contrast = useValue(contrastAtom) const more = useValue(prefersMoreContrastSelector) return {contrast} } ``` ## Exports | Export | Kind | Type | | ----------------------------- | ---------------- | ------------------------------------------------- | | `contrastAtom` | atom (read-only) | `Contrast` | | `prefersMoreContrastSelector` | selector | `boolean` | | `prefersLessContrastSelector` | selector | `boolean` | | `Contrast` | type | `"no-preference" \| "more" \| "less" \| "custom"` | ## Cross-framework `contrastAtom` is global: identical in every framework, only the read primitive's name changes (`useValue`, `createValue`, `injectValue`, `watch`, or `store.get` / `store.sub` in plain JS). The browser subscription starts on the first subscriber across all stores and stops when the last leaves. --- Source: https://valdres.dev/react/plugins/browser-device-motion.md # browser-device-motion Wraps the `devicemotion` event as global atoms — linear acceleration and rotation rate, plus derived selectors. Values are usually `null` on desktop. > **Permission** > > > iOS gates motion behind a user gesture — call `requestMotionPermission()` from a click before subscribing. ## Install ```bash bun add @valdres/browser-device-motion ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-device-motion](https://valdres.dev/react/plugins/browser-device-motion) ## Usage ```tsx import { useValue } from "valdres-react" import { accelerationSelector, rotationRateSelector, requestMotionPermission, } from "@valdres/browser-device-motion" function Motion() { const accel = useValue(accelerationSelector) // Vector3 | null const rot = useValue(rotationRateSelector) // RotationRateSnapshot | null return (
{JSON.stringify({ accel, rot }, null, 2)}
) } ``` ## Exports | Export | Kind | Type | | -------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- | | `motionAtom` | atom (read-only) | `MotionSnapshot \| null` | | `permissionAtom` | atom (read-only) | `PermissionValue` | | `motionStatusAtom` | atom (read-only) | `MotionStatus` | | `accelerationSelector` | selector | `Vector3 \| null` | | `accelerationIncludingGravitySelector` | selector | `Vector3 \| null` | | `accelerationMagnitudeSelector` | selector | `number \| null` | | `rotationRateSelector` | selector | `RotationRateSnapshot \| null` | | `intervalSelector` | selector | `number \| null` | | `requestMotionPermission` | util fn | `() => Promise` | | `MotionSnapshot` | type | `{ acceleration, accelerationIncludingGravity, rotationRate: ... \| null; interval: number; timeStamp: number }` | | `Vector3` | type | `{ x, y, z: number \| null }` | | `RotationRateSnapshot` | type | `{ alpha, beta, gamma: number \| null }` | | `MotionStatus` | type | `"unsupported" \| "idle" \| "active"` | | `PermissionValue` | type | `"granted" \| "denied" \| "prompt" \| "unsupported"` | ## Cross-framework The `devicemotion` subscription starts on the first subscriber across all stores and stops when the last one leaves. Read primitive per framework: `useValue` (React/Vue), `createValue` (Solid), `injectValue` (Angular), `watch` (Svelte), or `store.get` / `store.sub`. --- Source: https://valdres.dev/react/plugins/browser-device-orientation.md # browser-device-orientation Wraps `DeviceOrientationEvent` as global atoms, exposing the raw tilt snapshot plus `alpha` / `beta` / `gamma` and a compass-heading selector. > **Permission** > > > iOS requires a user gesture: call `requestOrientationPermission()` from a click handler before subscribing. Other browsers grant on subscribe. ## Install ```bash bun add @valdres/browser-device-orientation ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-device-orientation](https://valdres.dev/react/plugins/browser-device-orientation) ## Usage ```tsx import { useValue } from "valdres-react" import { compassHeadingSelector, requestOrientationPermission, } from "@valdres/browser-device-orientation" function Compass() { const heading = useValue(compassHeadingSelector) return ( <> {heading == null ? "—" : `${Math.round(heading)}°`} ) } ``` ## Exports | Export | Kind | Type | | ------------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `orientationAtom` | atom (read-only) | `OrientationSnapshot \| null` | | `permissionAtom` | atom (settable) | `PermissionValue` | | `orientationStatusAtom` | atom (read-only) | `OrientationStatus` | | `alphaSelector` | selector | `number \| null` | | `betaSelector` | selector | `number \| null` | | `gammaSelector` | selector | `number \| null` | | `absoluteSelector` | selector | `boolean \| null` | | `compassHeadingSelector` | selector | `number \| null` | | `requestOrientationPermission` | util fn | `() => Promise` | | `OrientationSnapshot` | type | `{ alpha, beta, gamma: number \| null; absolute: boolean; webkitCompassHeading, webkitCompassAccuracy: number \| null; timeStamp: number }` | | `OrientationStatus` | type | `"unsupported" \| "idle" \| "active"` | | `PermissionValue` | type | `"granted" \| "denied" \| "prompt" \| "unsupported"` | ## Cross-framework Global atoms/selectors — works in every framework, only the read primitive differs. The `deviceorientation` listener starts on the first subscriber and stops on the last. --- Source: https://valdres.dev/react/plugins/browser-focus.md # browser-focus Wraps [`document.hasFocus()`](https://developer.mozilla.org/docs/Web/API/Document/hasFocus) plus the window `focus` / `blur` events as one global atom. ## Install ```bash bun add @valdres/browser-focus ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-focus](https://valdres.dev/react/plugins/browser-focus) ## Usage ```tsx import { useValue } from "valdres-react" import { focusAtom } from "@valdres/browser-focus" function FocusBadge() { const focused = useValue(focusAtom) return {focused ? "Focused" : "Blurred"} } ``` ## Exports | Export | Kind | Type | | ----------- | ---------------- | --------- | | `focusAtom` | atom (read-only) | `boolean` | ## Cross-framework A global atom — works in every framework; only the read primitive's name changes (`useValue`, `createValue`, `injectValue`, `watch`, or `store.get` in plain JS). The `focus` / `blur` listeners attach on the first subscriber and detach when the last leaves. Returns `true` during SSR. Compose with `@valdres/browser-visibility` for a "user is present" signal (see `@valdres/browser-presence`). --- Source: https://valdres.dev/react/plugins/browser-geolocation.md # browser-geolocation Wraps the [Geolocation API](https://developer.mozilla.org/docs/Web/API/Geolocation_API) as reactive state: the current position, derived coordinate selectors, the permission state, and a status machine. The watch starts on the first subscriber and stops on the last. > **Permission & HTTPS** > > > Geolocation requires a secure context (HTTPS or localhost) and a user grant. Subscribing to `positionAtom` (or a coordinate selector) triggers the browser's permission prompt. ## Install ```bash bun add @valdres/browser-geolocation ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-geolocation](https://valdres.dev/react/plugins/browser-geolocation) ## Usage ```tsx import { useValue } from "valdres-react" import { coordsSelector, geolocationStatusAtom } from "@valdres/browser-geolocation" function Where() { const coords = useValue(coordsSelector) const status = useValue(geolocationStatusAtom) if (!coords) return {status} return {coords.latitude.toFixed(3)}, {coords.longitude.toFixed(3)} } ``` ## Exports | Export | Kind | Type | | -------------------------------------------------------- | ---------------- | ------------------------------------------------------------- | | `positionAtom` | atom (read-only) | `GeolocationSnapshot \| null` | | `coordsSelector` | selector | `{ latitude, longitude } \| null` | | `accuracySelector` | selector | `number \| null` | | `altitudeSelector` / `speedSelector` / `headingSelector` | selector | `number \| null` | | `permissionAtom` | atom (read-only) | `"granted" \| "denied" \| "prompt" \| "unsupported"` | | `geolocationStatusAtom` | atom (read-only) | `"idle" \| "pending" \| "active" \| "error" \| "unsupported"` | | `geolocationErrorAtom` | atom (read-only) | `GeolocationError \| null` | | `geolocationOptionsAtom` | atom (settable) | `PositionOptions` | ## Cross-framework All state is global atoms/selectors, so it works in every framework — only the read primitive differs. Write `geolocationOptionsAtom` (via your framework's set hook, or `store.set`) to change accuracy/timeout options; the watch re-subscribes automatically. --- Source: https://valdres.dev/react/plugins/browser-keyboard.md # browser-keyboard Tracks which keys are currently held down, from the document's `keydown` / `keyup` events. Exposes the raw pressed keys plus selectors for codes, modifiers, and per-key checks. ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-keyboard](https://valdres.dev/react/plugins/browser-keyboard) Press and hold keys — the on-screen keyboard reflects `pressedCodesSelector` live. ## Install ```bash bun add @valdres/browser-keyboard ``` ## Usage ```tsx import { useValue } from "valdres-react" import { pressedCodesSelector, isCodePressedSelector } from "@valdres/browser-keyboard" function Keys() { const codes = useValue(pressedCodesSelector) // KeyboardCode[] const shift = useValue(isCodePressedSelector("ShiftLeft")) return {codes.join(" + ")} } ``` ## Exports | Export | Kind | Type | | ----------------------------- | ---------------------- | ---------------- | | `pressedKeysAtom` | atom (read-only) | `PressedKey[]` | | `pressedCodesSelector` | selector | `KeyboardCode[]` | | `pressedKeyValuesSelector` | selector | `string[]` | | `modifierSelector` | selector | `Modifier[]` | | `isCodePressedSelector(code)` | selector family | `boolean` | | `isKeyPressedSelector(key)` | selector family | `boolean` | | `toggleKeyAtom(key)` | atom family (settable) | `boolean` | ## Cross-framework Global atoms and selectors — works in every framework. For higher-level "press this combination → run this callback" hotkeys (rather than raw key state), use `@valdres/hotkeys`. --- Source: https://valdres.dev/react/plugins/browser-online.md # browser-online Reactive online/offline status. Wraps `navigator.onLine` and the `online` / `offline` events as a single global atom that any framework can read. ## Install ```bash bun add @valdres/browser-online ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-online](https://valdres.dev/react/plugins/browser-online) ## Usage ```tsx import { useValue } from "valdres-react" import { onlineAtom } from "@valdres/browser-online" function ConnectionBadge() { const online = useValue(onlineAtom) return {online ? "Online" : "Offline"} } ``` The hook reads from the store provided by your app (see [Provider](https://valdres.dev/react/Provider)); the atom's value is kept in sync across every store automatically. ## Exports | Export | Kind | Type | | ------------ | ---------------- | --------- | | `onlineAtom` | atom (read-only) | `boolean` | ## Cross-framework `onlineAtom` is a global atom, so it works identically in every framework — only the read primitive's name changes (`useValue`, `createValue`, `injectValue`, `watch`, or `store.get` / `store.sub` in plain JavaScript). The browser subscription starts on the first subscriber across all stores and stops when the last one leaves. --- Source: https://valdres.dev/react/plugins/browser-presence.md # browser-presence Read-only boolean for "is the user actually here": `true` when the tab is visible **and** the window is focused. Composes [`browser-visibility`](https://valdres.dev/plugins/browser-visibility) and [`browser-focus`](https://valdres.dev/plugins/browser-focus). ## Install ```bash bun add @valdres/browser-presence ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-presence](https://valdres.dev/react/plugins/browser-presence) ## Usage ```tsx import { useValue } from "valdres-react" import { presenceSelector } from "@valdres/browser-presence" function PresenceDot() { const present = useValue(presenceSelector) return {present ? "Active" : "Away"} } ``` ## Exports | Export | Kind | Type | | ------------------ | -------------------- | --------- | | `presenceSelector` | selector (read-only) | `boolean` | ## Cross-framework `presenceSelector` is a global selector — read it with `store.get` / `store.sub` in plain JS. It recomputes whenever the visibility or focus subscription fires. --- Source: https://valdres.dev/react/plugins/browser-reduced-data.md # browser-reduced-data Wraps the `(prefers-reduced-data: reduce)` media query as a global atom, with a boolean selector. ## Install ```bash bun add @valdres/browser-reduced-data ``` ## Live example ▶ Live example: [https://valdres.dev/react/plugins/browser-reduced-data](https://valdres.dev/react/plugins/browser-reduced-data) ## Usage ```tsx import { useValue } from "valdres-react" import { prefersReducedDataSelector } from "@valdres/browser-reduced-data" function Hero() { const reduced = useValue(prefersReducedDataSelector) return reduced ? :