# How State Is Managed in the Karakeep UI: Zustand Architecture Explained

> Discover how Karakeep manages UI state with the lightweight Zustand library. Learn about its focused stores for fast, type-safe updates without Redux boilerplate.

- Repository: [Karakeep App/karakeep](https://github.com/karakeep-app/karakeep)
- Tags: architecture
- Published: 2026-07-07

---

**Karakeep uses Zustand, a lightweight state management library, to handle global UI state through small, focused stores located in `apps/web/lib/store/`, enabling fast, type-safe updates without the boilerplate of Redux or Context API.**

The Karakeep front-end is built with Next.js and React. Rather than relying on React Context or Redux for global state, the application implements a **Zustand**-based architecture that keeps state logic modular and performant. This approach allows components across the UI to share data—such as sort order, navigation state, and layout preferences—while maintaining minimal re-render overhead.

## The Zustand State Architecture

Karakeep leverages **Zustand 5.x** (defined as `"zustand": "^5.0.5"` in [`apps/web/package.json`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/package.json)) to manage all shared UI state. The architecture follows a **store-per-concern** pattern, where each feature or behavior owns its isolated state slice.

All stores live in the `apps/web/lib/store/` directory. Each file exports a custom hook created via Zustand’s `create` function, which components import to read or mutate state. This eliminates the need for providers or complex reducer logic, as Zustand stores exist outside the React component tree yet integrate seamlessly with hooks.

## Key State Stores in Karakeep

The application splits UI state into logical, single-responsibility stores. Each store handles a specific interaction domain:

- **`useSortOrderStore`** – Manages bookmark listing sort preferences (e.g., sorting by date or title). Defined in [`apps/web/lib/store/useSortOrderStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/lib/store/useSortOrderStore.ts).
- **`useKeyboardNavigationStore`** – Tracks arrow-key navigation state within the bookmark grid. Defined in [`apps/web/lib/store/useKeyboardNavigationStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/lib/store/useKeyboardNavigationStore.ts).
- **`useInSearchPageStore`** – Maintains flags indicating whether the current view is the search page and related UI states. Defined in [`apps/web/lib/store/useInSearchPageStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/lib/store/useInSearchPageStore.ts).
- **`useInBookmarkGridStore`** – Controls layout mode (list vs. grid) and selection state for bookmarks. Defined in [`apps/web/lib/store/useInBookmarkGridStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/lib/store/useInBookmarkGridStore.ts).

## Creating and Consuming Stores

Each store follows a consistent TypeScript pattern. The `create` function initializes state and exposes setter actions in a single definition.

### Store Definition Pattern

In [`apps/web/lib/store/useSortOrderStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/lib/store/useSortOrderStore.ts), the store is defined as:

```typescript
import { create } from "zustand";

export const useSortOrderStore = create<{
  sortBy: "date" | "title";
  setSortBy: (s: "date" | "title") => void;
}>((set) => ({
  sortBy: "date",
  setSortBy: (s) => set({ sortBy: s }),
}));

```

The `set` function provided by Zustand handles immutable updates. TypeScript generics ensure the store is fully typed, providing compile-time safety and IDE autocompletion.

### Component Integration

Components consume these stores using standard React hooks. For example, a sort dropdown component imports the hook and selects only the state slices it needs:

```tsx
import { useSortOrderStore } from "@/lib/store/useSortOrderStore";

export function SortDropdown() {
  const sortBy = useSortOrderStore((s) => s.sortBy);
  const setSortBy = useSortOrderStore((s) => s.setSortBy);

  return (
    <select value={sortBy} onChange={(e) => setSortBy(e.target.value as any)}>
      <option value="date">Date</option>
      <option value="title">Title</option>
    </select>
  );
}

```

Using selector functions like `(s) => s.sortBy` ensures components only re-render when their specific slice changes, optimizing performance.

## State Flow and Component Integration

State updates in Karakeep follow a synchronous, unidirectional flow. Because Zustand stores operate independently of the React context tree, updates propagate immediately to all subscribed components without prop drilling.

### Update Mechanism

Components can update state in two ways:

1. **Via the hook’s actions:**
   ```tsx
   const setSortBy = useSortOrderStore((s) => s.setSortBy);
   setSortBy("title");
   ```

2. **Via `getState()` for imperative updates:**
   ```tsx
   useSortOrderStore.getState().setSortBy("title");
   ```

### Example Flow

When a user clicks a "Sort by Title" button in [`apps/web/components/ui/sort-dropdown.tsx`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/components/ui/sort-dropdown.tsx):

1. The component calls `useSortOrderStore.getState().setSortBy("title")`.
2. Zustand updates the `sortBy` field and notifies all subscribers.
3. The bookmark grid component (in [`apps/web/components/ui/bookmark-grid.tsx`](https://github.com/karakeep-app/karakeep/blob/main/apps/web/components/ui/bookmark-grid.tsx)) detects the change through its subscription and re-renders with the new sort order.

This pattern ensures that **UI components remain pure presentation layers**, while all state logic resides in the dedicated store files under `apps/web/lib/store/`.

## Summary

- **State management** in Karakeep relies on **Zustand**, not Redux or Context API, providing minimal boilerplate and optimal performance.
- **Stores are modular** and located in `apps/web/lib/store/`, with each file handling a specific UI concern like sorting, navigation, or layout.
- **Type-safe hooks** generated by `create` allow components to read and update state with full TypeScript support.
- **Synchronous updates** via `set` or `getState()` ensure immediate UI consistency across the application.

## Frequently Asked Questions

### Why does Karakeep use Zustand instead of Redux or Context API?

Zustand provides **minimal boilerplate** compared to Redux, requiring only a single `create` call to define a store. It avoids the performance pitfalls of React Context—which can trigger extensive re-renders when state changes—by using atomic subscriptions. For Karakeep’s Next.js application, this results in faster updates and simpler maintenance without sacrificing type safety.

### Where are the Zustand stores defined in the Karakeep codebase?

All Zustand stores are defined in the `apps/web/lib/store/` directory. Key files include [`useSortOrderStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/useSortOrderStore.ts) for sorting preferences, [`useKeyboardNavigationStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/useKeyboardNavigationStore.ts) for grid navigation, [`useInSearchPageStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/useInSearchPageStore.ts) for search page flags, and [`useInBookmarkGridStore.ts`](https://github.com/karakeep-app/karakeep/blob/main/useInBookmarkGridStore.ts) for layout and selection states.

### How do components access and update state in Karakeep?

Components import the store hooks (e.g., `useSortOrderStore`) and call them with selector functions to read specific state slices. Updates occur by invoking the action methods defined in the store, either through the hook’s returned actions or imperatively via `store.getState().actionName()`. This pattern keeps components decoupled from state implementation details.

### Is the Karakeep UI state management type-safe?

Yes. All stores are implemented in TypeScript using Zustand’s generic `create` function, which allows developers to define strict interfaces for state shapes and action signatures. This ensures compile-time validation of state access and updates throughout the `apps/web/components/ui` directory.