Skip to main content
Claude Code’s entire UI is built with React + Ink — React for the terminal. This means components, hooks, context providers, and re-render optimization all work exactly as they do in a web app, with Ink translating React’s output to terminal escape sequences.

State management

State is managed through a custom store + React context pattern. The AppState object is the single source of truth for all mutable session state.

AppStateStore

src/state/AppStateStore.ts defines the shape of AppState — a large plain object covering:
  • Conversation messages and history
  • Active tool permission context (ToolPermissionContext)
  • MCP server connections and resources
  • Session metadata (model, effort, theme, cost tracking)
  • Background task states
  • Plugin and skill state
  • Attribution, file history, and speculation state
// Accessing state inside a tool
const appState = context.getAppState()

// Updating state inside a tool (immutable update pattern)
context.setAppState(prev => ({
  ...prev,
  someField: newValue,
}))
setAppState in sub-agents is a no-op for session-scoped infrastructure. Use setAppStateForTasks (when available) to reach the root store from a nested agent context.

Store

src/state/store.ts exports the Store<T> type — a lightweight observable wrapper around AppState:
export type Store<T> = {
  getState(): T
  setState(f: (prev: T) => T): void
  subscribe(listener: () => void): () => void
}

Context providers

src/context/ contains React context providers that expose slices of state or derived values to the component tree:
ProviderFilePurpose
Notificationsnotifications.tsxRate limits, deprecation warnings, permission alerts
Statsstats.tsxSession statistics (token counts, turn counts)
FPS metricsfpsMetrics.tsxTerminal render frame rate tracking
ModalmodalContext.tsxFull-screen modal overlay management
OverlayoverlayContext.tsxLayered overlay state
Queued messagesQueuedMessageContext.tsxBuffered messages awaiting render
Voicevoice.tsxVoice input/output state (feature-gated)

Selectors and change observers

  • src/state/selectors.ts — pure functions that derive computed values from AppState
  • src/state/onChangeAppState.ts — registers side-effects that fire when specific state keys change (e.g., persisting settings to disk when they are updated)

UI components (~140)

Components live in src/components/ and are built from:
  • Ink primitivesBox (flexbox layout), Text (styled text), useInput() (keyboard events)
  • Chalk — terminal color and style strings
  • React Compiler — enabled for optimized re-renders, reducing unnecessary updates in the message stream
  • Design system primitives — shared atoms in src/components/design-system/
React Compiler transforms component functions at build time to memoize them automatically, without requiring manual useMemo / useCallback calls.

Notable component groups

Components for rendering the conversation stream — assistant text, tool-use blocks, tool results, progress indicators, and code blocks with syntax highlighting.
The REPL input line with typeahead, vim mode, paste handling, and slash command autocomplete. Backed by hooks in src/hooks/ (useTextInput, useVimInput, usePasteHandler, useInputBuffer).
Inline permission prompts that pause tool execution and ask the user to approve, deny, or set a persistent rule. These integrate with src/hooks/toolPermission/.
src/components/design-system/ — primitive layout and typography components used across the rest of the UI to enforce visual consistency.

Screens

Full-screen UI modes live in src/screens/:
ScreenFilePurpose
REPLREPL.tsxMain interactive REPL — the default screen
DoctorDoctor.tsxEnvironment diagnostics launched by /doctor
Resume conversationResumeConversation.tsxSession restore picker launched by /resume
REPL.tsx is the root of the interactive session — it renders the message stream, input line, status bar, and any active overlays.

Hooks (~80)

src/hooks/ contains React hooks following standard patterns. Categories:
  • useCanUseTool — evaluates whether a tool call should be allowed given the current permission mode and rules
  • src/hooks/toolPermission/ — the full permission evaluation pipeline, including ML classifier integration, wildcard rule matching, and user prompt rendering
  • useIDEIntegration — detects and manages the active IDE bridge connection
  • useIdeConnectionStatus — reactive connection status for the IDE extension
  • useDiffInIDE — opens file diffs in the connected IDE
  • useTextInput — controlled text input with cursor management
  • useVimInput — vim-mode input state machine
  • usePasteHandler — multi-line paste detection and handling
  • useInputBuffer — raw keyboard buffer management
  • useSessionBackgrounding — handles the session going to the background (e.g., Ctrl+Z)
  • useRemoteSession — manages remote session state for the bridge/teleport features
  • useAssistantHistory — up-arrow history for the input line
  • useManagePlugins — plugin installation, removal, and reload
  • useSkillsChange — reacts to skill directory changes and reloads the command list
src/hooks/notifs/ — reactive hooks for displaying rate limit warnings, deprecation notices, and other transient notifications in the status bar.

Configuration schemas

Zod schemas (src/schemas/)

All configuration is validated with Zod schemas (using the zod/v4 API):
SchemaPurpose
User settingsPer-user preferences (model, theme, keybindings, etc.)
Project settingsPer-project configuration (CLAUDE.md, allowed tools, etc.)
Organization policiesEnterprise MDM policy enforcement
Permission rulesalwaysAllow, alwaysDeny, alwaysAsk rule sets

Migrations (src/migrations/)

When the config schema changes between versions, migration functions in src/migrations/ read the old format and transform it to the current schema. This runs automatically at startup when an outdated config file is detected.

See also

  • Architecture overview — How the UI and state layers fit into the full pipeline
  • Query Engine — How getAppState / setAppState are threaded through tool execution
  • Tool System — How tools access and mutate AppState via ToolUseContext

Build docs developers (and LLMs) love