kamod-signals · Persisted state · Familiar .value APIInstall · Compose · Explore
Persisted Preact signals for every storage driver
Reactive state with durable storage — localStorage, sessionStorage, IndexedDB, cookies, and memory — while keeping the familiar @preact/signals .value API.
Decide which values should survive a reload before choosing a storage driver. Use a stable key, define a useful initial value, and keep the lifetime of the signal aligned with the screen, session or application that owns it. Pair it with the component library when building your interface.
In this guide 9 sections
What the package brings
Use persistence for values that should outlive their current render. Decide what the preference belongs to, how long it should survive and how a user can return to the default.
Choose the right approach
Choose local storage for small, non-sensitive browser preferences.
A saved browser value is neither account synchronization nor authoritative server data.
Choose session storage when the lifetime should follow that browser session.
Document which values survive refreshes and which should disappear on sign-out.
Review the cookie driver and createCookieContext at the request boundary.
Cookie scope, response headers and request isolation belong to the server integration.
Persistence built in
persistedSignal and usePersistedSignal sync reactive values to the driver you choose.
Give each preference a stable storage key and a useful initial value. Treat future changes to the stored shape as a migration decision, rather than assuming every returning visitor has fresh data.
Framework friendly
Works beside Preact components and plain modules — share state across controllers without prop drilling.
Share a signal only when its consumers should share the same value. Keep temporary screen state local, and avoid copying persisted values into a second independently writable store.
SSR-aware cookies
createCookieContext and serializeCookie helpers keep cookie-backed signals usable on the server.
Keep request-specific cookie handling at the server boundary. Verify that the server and client agree on the initial value, and check what happens when there is no saved preference.
Installation
Install @kamod-ch/signals with @preact/signals and Preact as peer dependencies when using the package outside the Kamod UI monorepo.
pnpm add @kamod-ch/signals @preact/signals preactCheck your environment
Use your existing package manager and keep the peer dependencies aligned with your application. Compare package.json and your lockfile with the documentation for the version you install; add the package once at the workspace boundary that uses it.
Keep the first change small. Start with the usage example, run your project’s typecheck and build, then connect it to a real screen. The package overview helps you decide how companion libraries fit together.
Usage
Create a persisted signal with a storage key and driver, then read or write .value as usual. Use usePersistedSignal inside components for scoped instances.
import { persistedSignal } from "@kamod-ch/signals";
export const theme = persistedSignal("theme", "dark", { storage: "local" });
// later
theme.value = "light";Read the example
Follow the value from its definition to the interface. Each part below explains a decision to keep when adapting the example.
The key identifies browser storage, while the variable identifies the live signal. Decide whether it is a device preference or belongs to a signed-in account before sharing the key across screens or sessions.
const theme = persistedSignal("theme", "dark", { storage: "local" });Consumers should observe the same instance. Copying the value into unrelated local state creates a second owner that can become stale. Keep derived presentation derived, rather than synchronizing two writable values with effects.
theme.value = "light";Reset restores the initial value; clearing a stored entry is a separate operation. Use an explicit user action for preference resets, and verify the behavior after reload rather than only checking the currently rendered label.
theme.reset();Keep a preference local to a component
A custom hook can keep the persistent preference close to the settings control. The versioned key below leaves room for a future storage-shape change. This example is intentionally a harmless preference, not authentication or private user data.
import { usePersistedSignal } from "@kamod-ch/signals";
import { Button } from "@kamod-ch/ui";
export function DensityPreference() {
const compact = usePersistedSignal("ui:compact:v1", false, { storage: "local" });
return (
<div class="flex flex-wrap gap-2">
<Button aria-pressed={compact.value} onClick={() => { compact.value = !compact.value; }}>
Compact spacing
</Button>
<Button variant="ghost" onClick={() => compact.reset()}>Restore default</Button>
</div>
);
}Try the boundaries. Toggle, reload and restore the default. Then test without a stored value and with browser storage restricted. For SSR, ensure the first client render agrees with the server; browser-only preferences cannot automatically be known by the server.
Integrate with your application
Persist preferences deliberately. A color scheme or density setting has a different lifetime from a draft or an account-specific value. Name keys clearly, decide when they should be reset and avoid retaining temporary UI state without a reason.
The module-level usage example demonstrates a shared signal. In a server-rendered application, do not share user-specific mutable state across requests. Follow the package’s SSR and cookie guidance when a preference must be available before hydration.
Keep reads and writes through the signal’s .value interface and review how your chosen driver handles unavailable storage. Plan for old stored values when you change a preference’s shape or allowed options.
Choose the owner and lifetime
Name keys by purpose and treat a stored shape as a small data contract. Give defaults the same care as saved values. Decide whether clearing a preference means removing storage, resetting the live value, or both. For account-specific settings, define sign-out behavior and avoid silently applying one user's saved value to the next user on the device.
Account for the environment
Storage availability, hydration and cross-tab updates are separate concerns. Test the chosen driver in the actual environment instead of assuming all storage behaves like synchronous local storage. If a preference changes layout, make the initial server and client output compatible and avoid a second competing copy of the value in component state.
Connect the pieces. Reducer state — Use explicit events and transitions when a workflow needs more structure than a persisted preference.
When something behaves differently
Start with the smallest failing interaction. Compare a fresh page with the same page after a change or reload, and keep the package version in your reproduction.
- A value changes back after refresh
- Check the storage driver, key and write path. Make sure the code updates
.valueon the intended persisted signal rather than a separate temporary variable. - A returning visitor sees invalid data
- Inspect the saved shape from older releases. Validate or migrate before using it, and provide a reset path when the value cannot be recovered.
- Two controls disagree
- Confirm that they are meant to share a source and review the driver's synchronization behavior. Matching variable names do not guarantee matching signal instances or storage keys.
If the behavior still differs from the documented API, check existing issues before opening a report. Include the expected result, actual result, package version and a small reproduction without private application data.
API Reference
This page is a Kamod UI overview. Full API docs, driver tables, and SSR cookie examples live on the dedicated kamod-signals docs.
Use this page to understand the integration, then consult the Full API reference on live docs for exact signatures and supported options. Compare those details with the version in your lockfile before adapting an example.
Explore drivers, API, and examples
Open the kamod-signals docs for getting started, storage showcases, and SSR cookie guides.
- Before choosing an API: read its input types, return values and default behavior.
- Before shipping: review lifecycle or server-rendering notes for the features you use.
- When behavior differs: reduce the case to a small example and include your package version in the report.
Accessibility Notes
When signals drive UI chrome (theme, sidebar, locale), reflect state in accessible controls and keep preference changes predictable for keyboard and assistive tech users.
Before you ship
- Try a fresh visit, a saved preference and an explicit reset.
- Test your chosen driver with unavailable storage and unexpected stored values.
- Verify that account or request-specific data cannot leak through shared module state.
Review the result with real content and keyboard input. Keep visible labels, loading and error feedback, and focus behavior in sync with the state your application exposes.
Take the guide with you
Keep the examples, integration decisions and troubleshooting notes together in your project, or share them with a coding assistant. This reference works with any assistant; no model-specific setup is needed. Check the installed version and your project’s conventions before applying a suggestion.
Display changes only the view. Copy and download include the complete Markdown reference.
# Signals — integration reference
Reactive state with durable storage — localStorage, sessionStorage, IndexedDB, cookies, and memory — while keeping the familiar @preact/signals .value API.
Package: @kamod-ch/signals
## Choose an approach
Use persistence for values that should outlive their current render. Decide what the preference belongs to, how long it should survive and how a user can return to the default.
### A preference across visits
Choose local storage for small, non-sensitive browser preferences.
A saved browser value is neither account synchronization nor authoritative server data.
### A shorter-lived browser value
Choose session storage when the lifetime should follow that browser session.
Document which values survive refreshes and which should disappear on sign-out.
### Server-aware persistence
Review the cookie driver and createCookieContext at the request boundary.
Cookie scope, response headers and request isolation belong to the server integration.
## Installation
Install @kamod-ch/signals with @preact/signals and Preact as peer dependencies when using the package outside the Kamod UI monorepo.
```bash
pnpm add @kamod-ch/signals @preact/signals preact
```
## Starting example
```tsx
import { persistedSignal } from "@kamod-ch/signals";
export const theme = persistedSignal("theme", "dark", { storage: "local" });
// later
theme.value = "light";
```
### Give the saved value a clear owner
The key identifies browser storage, while the variable identifies the live signal. Decide whether it is a device preference or belongs to a signed-in account before sharing the key across screens or sessions.
```tsx
const theme = persistedSignal("theme", "dark", { storage: "local" });
```
### Read and write through one signal
Consumers should observe the same instance. Copying the value into unrelated local state creates a second owner that can become stale. Keep derived presentation derived, rather than synchronizing two writable values with effects.
```tsx
theme.value = "light";
```
### Provide an explicit reset
Reset restores the initial value; clearing a stored entry is a separate operation. Use an explicit user action for preference resets, and verify the behavior after reload rather than only checking the currently rendered label.
```tsx
theme.reset();
```
## Keep a preference local to a component
A custom hook can keep the persistent preference close to the settings control. The versioned key below leaves room for a future storage-shape change. This example is intentionally a harmless preference, not authentication or private user data.
File: src/components/DensityPreference.tsx
```tsx
import { usePersistedSignal } from "@kamod-ch/signals";
import { Button } from "@kamod-ch/ui";
export function DensityPreference() {
const compact = usePersistedSignal("ui:compact:v1", false, { storage: "local" });
return (
<div class="flex flex-wrap gap-2">
<Button aria-pressed={compact.value} onClick={() => { compact.value = !compact.value; }}>
Compact spacing
</Button>
<Button variant="ghost" onClick={() => compact.reset()}>Restore default</Button>
</div>
);
}
```
Toggle, reload and restore the default. Then test without a stored value and with browser storage restricted. For SSR, ensure the first client render agrees with the server; browser-only preferences cannot automatically be known by the server.
## Ownership and environment
Name keys by purpose and treat a stored shape as a small data contract. Give defaults the same care as saved values. Decide whether clearing a preference means removing storage, resetting the live value, or both. For account-specific settings, define sign-out behavior and avoid silently applying one user's saved value to the next user on the device.
Storage availability, hydration and cross-tab updates are separate concerns. Test the chosen driver in the actual environment instead of assuming all storage behaves like synchronous local storage. If a preference changes layout, make the initial server and client output compatible and avoid a second competing copy of the value in component state.
Persistence needs a reset policy as well as a save path. Decide which values remain after sign-out, which expire with the session and which should never be stored at all.
## Troubleshooting
### A value changes back after refresh
Check the storage driver, key and write path. Make sure the code updates .value on the intended persisted signal rather than a separate temporary variable.
### A returning visitor sees invalid data
Inspect the saved shape from older releases. Validate or migrate before using it, and provide a reset path when the value cannot be recovered.
### Two controls disagree
Confirm that they are meant to share a source and review the driver's synchronization behavior. Matching variable names do not guarantee matching signal instances or storage keys.
## Resources
- [Documentation](https://kamod-ch.github.io/kamod-signals/)
- [Source](https://github.com/kamod-ch/signals)
- [npm](https://www.npmjs.com/package/@kamod-ch/signals)
Sources & attribution
This integration guide accompanies @kamod-ch/signals, maintained in the Kamod ecosystem. The dedicated documentation is the reference for package APIs; examples here show how those APIs fit into a Kamod UI application.
When copying or distributing source, retain the applicable license and attribution notices from the version you use. Check the published package alongside your lockfile when comparing an example with a newer release.