Contents

Panels · Guides

Bring your own store

Keep window state in zustand, TanStack Store or anything with three methods.

By default WindowManager keeps its own state. Pass store to keep it wherever the rest of your app's state lives. A store needs only three methods:

interface Store<S> {
  getState(): S;
  setState(state: S): void;
  subscribe(listener: () => void): () => void;
}

The engine is a pure reducer, reduce(state, action) → state, so nothing else is needed: the manager reads with getState, writes the reducer's result with setState, and React subscribes through useSyncExternalStore.

zustand

A vanilla zustand store already has exactly this shape:

import { createStore } from "zustand/vanilla";
import { createWorkspace, WindowManager, type WorkspaceState } from "@nynxz/react-panels";

const windows = createStore<WorkspaceState>(() => createWorkspace());

<WindowManager store={windows}>…</WindowManager>;

TanStack Store

A few lines of adapter (for @tanstack/store 0.9 and later, where subscribe returns a subscription object):

import { Store as TanStackStore } from "@tanstack/store";

const tanstack = new TanStackStore(createWorkspace());
const windows: Store<WorkspaceState> = {
  getState: () => tanstack.state,
  setState: (next) => tanstack.setState(() => next),
  subscribe: (listener) => {
    const subscription = tanstack.subscribe(() => listener());
    return () => subscription.unsubscribe();
  },
};

What reaches your store

Gestures don't flood it. A drag or resize lives in the component until release and is written once; the live tiling preview during a drag is UI, not state, and never reaches the store. Dragging a gutter writes the ratio at most once per animation frame, since the tiles on both sides must follow it.

Several managers can share one store: the taskbar at the top of this site is a separate WindowManager from the desktop below it, over the same store, and they act as one.