Where Voltix fits, and how it performs. If you already know you want per-key subscriptions in a single store, skip to Getting started.
Voltix keeps state in stores of keys, with listeners per key. A component names the keys it reads, and a write re-renders the readers of the keys that changed.
// Voltix: subscribe to a key const name = useStoreKey(user, 'name'); // Zustand: subscribe with a selector function const name = useUserStore((state) => state.name);
For a single field the two behave the same. The difference shows as reads widen. For several keys Voltix still takes an array, useStoreSelector(user, ['name', 'plan']), while a Zustand selector that returns an object needs an equality strategy (useShallow, memoized selectors) to avoid re-rendering on every write.
useStoreSelector(user, ['name', 'plan'])
useShallow
Store-engine microbenchmarks (store engines without React), in operations per second, higher is faster. Every library runs its production code. Each case runs in its own process and reports the median of 40 runs of about 10 ms each, and the table keeps each case's best of five passes (Node 26, Apple M4 Pro). Reproduce with npm run bench (`bench/vs.ts`).
npm run bench
x => x + 1
<sub>† Zustand's fine-grained row uses separate stores; its single-store selector pattern is O(N) in the number of subscribers. ‡ Valtio batches notifications asynchronously and its per-key subscribe is O(N). The transaction row uses each library's batch where it has one; a signal is a smaller unit than a keyed store, which is what the creation row shows.</sub>
Every library here re-renders only the components whose data changed. The throughput gap shows up under a high rate of writes: drags, streaming data, animation, large grids of independently updating cells.
Pick Voltix for views made of many independent values where each write should re-render only its readers: grids and tables with per-row state, editors with many fields, dashboards with live numbers, anything written at high frequency. Stores compose into a tree (nested stores with select(key), a store per id with at(id)), so a large app stays one tree. The Patterns page shows the request, polling, and live-table shapes end to end.
select(key)
at(id)
Cases where another shape fits better:
See Getting started to try it, or the API reference for the full surface.