# How to Use OpenClaude's React/Ink Terminal UI: A Developer's Guide

> Learn to build terminal UIs with React Ink and OpenClaude. Use React components to create interactive command-line experiences with ANSI-styled text. Get started today.

- Repository: [Gitlawb/openclaude](https://github.com/Gitlawb/openclaude)
- Tags: how-to-guide
- Published: 2026-09-05

---

**OpenClaude renders its interactive command-line experience with React Ink, letting you build terminal UIs using React components that output ANSI-styled text.**

The React/Ink terminal UI in OpenClaude powers everything from multi-line prompt editing to streaming message rendering and tool-specific visualizations. This guide walks through the component architecture, key implementation files, and practical ways to extend or embed the UI.

---

## Understanding OpenClaude's React Ink Architecture

OpenClaude's UI layer follows a clean separation between the **Ink runtime**, **React components**, and **tool-specific renderers**. The system bootstraps in [`src/entrypoints/init.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/entrypoints/init.ts), which calls `render(<App />)` via the utility in [`src/utils/staticRender.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/staticRender.tsx).

### Core Layers

| Layer | Responsibility | Key Implementation |
|-------|---------------|---------------------|
| **Ink runtime** | Boots the render loop, creates root `App` component, provides `useApp` hook for graceful shutdown | [`src/utils/staticRender.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/staticRender.tsx) |
| **Root UI** | Top-level layout, screen navigation (startup, prompt, spinner, results) | [`src/components/StartupScreen.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/components/StartupScreen.tsx) |
| **Prompt input** | Line editing, multi-line mode, auto-completion, floating hint icons | `src/components/PromptInput/*` |
| **Message rendering** | Streams assistant/tool output, "thinking" blocks, progressive updates | [`src/utils/exportRenderer.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/exportRenderer.tsx) |
| **Tool UI** | Per-tool Ink components formatting results in transcript | `src/tools/*/UI.tsx` |
| **State & permissions** | Central store for transcript, session metadata, confirmation dialogs | `src/components/permissions/*` |
| **Visual helpers** | Color palette, spinners, custom selects, hyperlinks | `src/components/Spinner/*`, `src/components/CustomSelect/*` |

The **tools registry** (implemented in `src/entrypoints/sdk/*`) passes down to all components, allowing any tool to render its own Ink UI. The `useApp` hook enables programmatic exit via `app.exit()` after completion or error.

---

## Key Files for React Ink Development

These files form the backbone of OpenClaude's terminal UI:

- **[`src/utils/staticRender.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/staticRender.tsx)** — Sets up Ink's `render` loop and captures stdout for testing
- **[`src/components/StartupScreen.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/components/StartupScreen.tsx)** — First view on launch, handles profile selection
- **`src/components/PromptInput/*`** — Input handling with [`usePromptInputPlaceholder.ts`](https://github.com/Gitlawb/openclaude/blob/main/usePromptInputPlaceholder.ts) and [`inputModes.ts`](https://github.com/Gitlawb/openclaude/blob/main/inputModes.ts)
- **[`src/utils/exportRenderer.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/exportRenderer.tsx)** — Streams React nodes as ANSI chunks for terminal display
- **`src/tools/*/UI.tsx`** — Tool-specific fragments like [`BashTool/UI.tsx`](https://github.com/Gitlawb/openclaude/blob/main/BashTool/UI.tsx) and [`WebFetchTool/UI.tsx`](https://github.com/Gitlawb/openclaude/blob/main/WebFetchTool/UI.tsx)
- **[`src/entrypoints/init.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/entrypoints/init.ts)** — CLI entry point that wires tool registry to Ink

---

## Creating Custom Ink Components

You can embed custom React Ink components anywhere in OpenClaude's rendering pipeline.

### Example: Custom Message Component

```typescript
// src/components/MyMessage.tsx
import { Box, Text } from '../../ink.js';
import { useEffect } from 'react';

export default function MyMessage({ text }: { text: string }) {
  useEffect(() => {
    // Simulate a delayed thinking status
    setTimeout(() => console.log('[thinking]'), 500);
  }, []);

  return (
    <Box flexDirection="column" marginBottom={1}>
      <Text color="cyan">💡 {text}</Text>
    </Box>
  );
}

```

### Integrating into Tool Output

Use `renderReactNode` from the export renderer to display your component during tool execution:

```typescript
// src/tools/CustomTool/CustomTool.tsx
import MyMessage from '../../components/MyMessage.js';
import { renderReactNode } from '../../utils/exportRenderer.js';

export async function runCustomTool(input: string) {
  // Display custom UI during processing
  await renderReactNode(<MyMessage text="Processing your request…" />);
  
  // Continue with normal tool logic
  const result = await processInput(input);
  return result;
}

```

The `renderReactNode` utility in [`src/utils/exportRenderer.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/exportRenderer.tsx) handles streaming ANSI chunks to the terminal, ensuring your React component renders correctly within the existing transcript.

---

## Running OpenClaude's React Ink UI Programmatically

Spawn the CLI to display the full interactive UI—including spinners, prompts, and tool results:

```typescript
import { exec } from 'node:child_process';

// Launch OpenClaude with Ink terminal UI
exec('bun run openclaude --model=anthropic/claude-3-opus', (err, stdout, stderr) => {
  if (err) {
    console.error('CLI failed:', err);
    return;
  }
  console.log('CLI finished. Output captured:');
  console.log(stdout);
});

```

Since Ink renders directly to the terminal, the spawned process provides the complete interactive experience identical to manual invocation.

---

## Customizing Visual Elements

### Building a Custom Spinner

The spinner system uses `useStalledAnimation` for frame timing:

```typescript
// src/components/Spinner/MySpinner.tsx
import { useStalledAnimation } from '../../Spinner/useStalledAnimation.js';
import { Box, Text } from '../../ink.js';

export function MySpinner() {
  const { frames, index } = useStalledAnimation({ speed: 120 });
  return (
    <Box>
      <Text>{frames[index]}</Text>
    </Box>
  );
}

```

To activate your custom spinner, export it as default in [`src/components/Spinner/index.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/components/Spinner/index.ts):

```typescript
// src/components/Spinner/index.ts
export { MySpinner as default } from './MySpinner.js';

```

---

## Working with the Prompt Input System

The **PromptInput** directory (`src/components/PromptInput/*`) contains several specialized hooks and utilities:

- **[`usePromptInputPlaceholder.ts`](https://github.com/Gitlawb/openclaude/blob/main/usePromptInputPlaceholder.ts)** — Manages placeholder text and hint visibility
- **[`inputModes.ts`](https://github.com/Gitlawb/openclaude/blob/main/inputModes.ts)** — Defines single-line, multi-line, and auto-completion modes
- **Floating "fast-icon" hints** — Contextual shortcuts displayed during input

These components leverage Ink's `useInput` hook for terminal key handling while maintaining React's component model.

---

## Tool-Specific UI Patterns

Each tool in OpenClaude can expose its own **Ink UI component**. The convention places these in `src/tools/{ToolName}/UI.tsx`:

- [`BashTool/UI.tsx`](https://github.com/Gitlawb/openclaude/blob/main/BashTool/UI.tsx) — Formats shell command output with syntax highlighting
- [`WebFetchTool/UI.tsx`](https://github.com/Gitlawb/openclaude/blob/main/WebFetchTool/UI.tsx) — Renders fetched content with source attribution
- [`MCPTool/UI.tsx`](https://github.com/Gitlawb/openclaude/blob/main/MCPTool/UI.tsx) — Displays MCP server interactions

Tool UI components receive the tool's result data as props and return Ink elements that integrate into the transcript stream.

---

## Summary

- OpenClaude's React Ink terminal UI is bootstrapped from [`src/entrypoints/init.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/entrypoints/init.ts) using [`src/utils/staticRender.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/staticRender.tsx) as the Ink runtime entry point
- Components live under `src/components/` with specialized directories for **PromptInput**, **Spinner**, **permissions**, and **CustomSelect**
- Tool-specific UIs follow the `src/tools/*/UI.tsx` convention and receive data via the tools registry in `src/entrypoints/sdk/*`
- Use `renderReactNode` from [`src/utils/exportRenderer.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/exportRenderer.tsx) to stream custom React components as ANSI output
- The `useApp` hook provides programmatic control over CLI lifecycle including graceful exit

---

## Frequently Asked Questions

### How do I add a custom component to OpenClaude's terminal UI?

Create a React component using Ink's `Box` and `Text` elements in `src/components/`, then import and render it via `renderReactNode` from [`src/utils/exportRenderer.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/exportRenderer.tsx). For tool-specific visuals, place the component in `src/tools/{YourTool}/UI.tsx` following the existing pattern.

### Can I run OpenClaude without the interactive React Ink interface?

The CLI defaults to Ink rendering when invoked normally. For non-interactive or headless usage, you would need to modify the entry point in [`src/entrypoints/init.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/entrypoints/init.ts) or spawn the process with output capture—though this limits functionality since many features depend on the interactive terminal UI.

### What version of Ink does OpenClaude use?

OpenClaude imports from `'../../ink.js'` (and similar relative paths), indicating a bundled or vendored Ink distribution. Check [`package.json`](https://github.com/Gitlawb/openclaude/blob/main/package.json) in the root of the repository for the exact semantic version, or examine [`src/utils/staticRender.tsx`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/staticRender.tsx) for any Ink-specific API usage that might indicate version requirements.

### How do permission dialogs work in the React Ink UI?

Permission dialogs pause the render loop for user confirmation using components in `src/components/permissions/*`. The `TrustDialog` utilities in [`src/components/TrustDialog/utils.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/components/TrustDialog/utils.ts) handle the visual presentation, while state management coordinates with `useApp` to block execution until the user responds.