> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bitfield.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Keep private UI state private

> Keep visual-only state local, and move only cross-package coordination state into Bitfield.

<div className="bf-article">
  <p className="bf-lead">
    Not every state value belongs in Bitfield. Put state in Bitfield when it coordinates packages. Keep hover, menu, drag, focus, and draft UI details private.
  </p>

  A sidebar has hover state, an editor has unsaved text, a file tree has a selected file, and an assistant panel needs the selected file for context. These are not the same kind of state.

  The split is simple: Bitfield carries product facts that multiple packages need, while components keep visual-only details close to where they happen.

  ## The rule

  ```text theme={null}
  Only this component cares -> keep it private
  Many packages need to react -> put it in Bitfield
  ```

  ## Traditional mistake

  ```ts theme={null}
  await writeBitfieldState('hovered-sidebar-item', itemId);
  await writeBitfieldState('dropdown-open', true);
  await writeBitfieldState('drag-position', { x, y });
  ```

  This turns visual noise into product state. It creates unnecessary churn and makes the product harder to reason about.

  ## Bitfield shape

  ```tsx theme={null}
  function SidebarItem() {
    const [isHovered, setHovered] = useState(false);
    return <button onMouseEnter={() => setHovered(true)} />;
  }
  ```

  Private UI state stays private.

  Shared product state becomes Bitfield state:

  ```text theme={null}
  selected-file
  current-project
  active-conversation
  selected-agent
  ```

  ## The decision table

  | State                                                         | Where it belongs         | Why                                           |
  | ------------------------------------------------------------- | ------------------------ | --------------------------------------------- |
  | Hovered button                                                | Private UI state         | Only the button cares.                        |
  | Dropdown open                                                 | Private UI state         | Usually only one component cares.             |
  | Current drag position                                         | Private UI state         | It changes too often and is visual.           |
  | Unsaved textarea draft                                        | Usually private UI state | It is not real product state until submitted. |
  | Selected file used by editor and AI context                   | Bitfield state           | Multiple packages coordinate around it.       |
  | Current project used by many panels                           | Bitfield state           | It changes what many packages show.           |
  | Active conversation used by chat, timeline, and notifications | Bitfield state           | It is a shared product fact.                  |

  ## Four decisions in real product code

  ### Hovered row

  ### Traditional over-globalized answer

  ```ts theme={null}
  await writeBitfieldState('hovered-row', rowId);
  ```

  ### Keep hover local

  ```tsx theme={null}
  const [hoveredRow, setHoveredRow] = useState<string | null>(null);
  ```

  The hover is not a product fact. It is only a visual detail for the current surface.

  ### Open command menu

  ### Traditional over-globalized answer

  ```ts theme={null}
  globalUiStore.commandMenuOpen = true;
  ```

  ### Keep menu state local

  ```ts theme={null}
  setCommandMenuOpen(true);
  ```

  Keep it private unless another package has a real product reason to observe it.

  ### Selected file

  ### Traditional under-shared answer

  ```tsx theme={null}
  const [selectedFile, setSelectedFile] = useState<string | null>(null);
  ```

  Better answer when editor, assistant panel, breadcrumbs, and command palette need it:

  ```ts theme={null}
  const selectedFile = readInput('selected-file');
  ```

  That value coordinates packages, so it should not be trapped inside one component.

  ### Draft text

  ### Traditional over-shared answer

  ```ts theme={null}
  await writeBitfieldState('draft-message-text', text);
  ```

  ### Keep draft text local

  ```tsx theme={null}
  const [draftText, setDraftText] = useState('');
  ```

  Only promote it when the draft becomes product data, such as a saved message, note, command, or package record.

  ## Promotion rules

  | Ask this                                                           | If yes                                | If no                                                              |
  | ------------------------------------------------------------------ | ------------------------------------- | ------------------------------------------------------------------ |
  | Does another package need this value?                              | Consider Bitfield state or data name. | Keep it private.                                                   |
  | Does changing it run real product work?                            | Send an action request.               | Keep it private or write product state through the right boundary. |
  | Is it only animation, hover, focus, drag, scroll, or draft typing? | Keep it private.                      | Continue classifying.                                              |
  | Would losing it on refresh lose customer-visible data or progress? | It may be product state.              | It is probably UI state.                                           |
  | Would two packages disagree if they each kept a copy?              | Put one public fact in Bitfield.      | Keep package-local state.                                          |

  ## What this prevents

  | Bad shape                                                    | Why it breaks                                           | Better shape                              |
  | ------------------------------------------------------------ | ------------------------------------------------------- | ----------------------------------------- |
  | Every UI event writes Bitfield state.                        | Shared state becomes noisy and expensive to understand. | Keep visual-only state private.           |
  | Shared product facts stay in React state.                    | Other packages cannot reliably react.                   | Put coordination state in Bitfield.       |
  | The shell owns product state because it sits above children. | The shell becomes the product brain.                    | Packages read shared facts from Bitfield. |
  | AI stores everything globally.                               | The app has too many sources of truth.                  | Classify state before writing code.       |

  React is one adapter example. Swift view state, Kotlin state, terminal prompt state, and future shell state follow the same rule: visual-only state stays near the UI that uses it.

  ## Review checklist

  | Check                                                     | Good answer                                     |
  | --------------------------------------------------------- | ----------------------------------------------- |
  | Is the value hover, focus, scroll, drag, or draft typing? | Keep it private.                                |
  | Does another package need it to make product decisions?   | Promote it to a shared fact or data name.       |
  | Does changing it run work?                                | Use an action request.                          |
  | Is React context being used as product coordination?      | Replace it with Runtime Kit reads and requests. |

  ## Full before and after

  ### Traditional feature request

  "When the user clicks a file row, highlight it, open it in the editor, and show the assistant panel context."

  ### Bad implementation

  ```tsx theme={null}
  const [hoveredRow, setHoveredRow] = useGlobalStore('hoveredRow');
  const [selectedFile, setSelectedFile] = useState<string | null>(null);

  function onClick(filePath: string) {
    setSelectedFile(filePath);
  }
  ```

  This gets both decisions backwards. Hover is visual-only but became global. Selected file coordinates packages but stayed trapped in one component.

  ### Put each value where its readers are

  ```tsx theme={null}
  const [hoveredRow, setHoveredRow] = useState<string | null>(null);
  const selectedFile = useBitfieldData<string>('selected-file');
  ```

  Then the click path updates the public selected-file fact through the package's public files and names. The editor and assistant panel read that fact. The hover remains private.

  The rule is not "local state bad" or "Bitfield state good." The rule is: put each value where its readers are.

  ## Review check

  Before adding state, classify it:

  ```text theme={null}
  Is this only visual?
    keep it private

  Does another package need to read it?
    make it Bitfield state

  Does changing it require work to run?
    send an action request
  ```

  ## Next

  * Learn shared state: [Share state between packages](/runtime-kit/build-without-tangled-code/share-state-between-packages)
  * Learn action requests: [Ask another package to do work](/runtime-kit/build-without-tangled-code/ask-another-package-to-do-work)
  * Understand local customer-visible state: [Local state](/runtime-kit/local-state)
</div>
