Ink Rendering
How Claude Code renders its terminal UI using a custom React renderer.
What Is Ink?
Ink is a custom React renderer that targets the terminal instead of the DOM. It lets you write terminal UIs using React components and JSX.
Shared from "Claude-Code" on Inkdown
How Claude Code renders its terminal UI using a custom React renderer.
Ink is a custom React renderer that targets the terminal instead of the DOM. It lets you write terminal UIs using React components and JSX.
The user's component tree is wrapped in context providers:
App — stdin, keyboard, mouse, suspend/resumeThemeProvider — theme contextTerminalSizeContext — terminal dimensionsTerminalFocusContext — focus stateAfter React commits, resetAfterCommit fires:
Yoga is a flexbox layout engine compiled to WASM. Every Ink DOM node has an associated Yoga node:
The renderer walks the DOM tree and writes to an Output buffer:
Blit optimization: If a subtree hasn't changed, it's bulk-copied from the previous frame's screen buffer instead of re-rendering children:
The new frame is diffed against the previous frame:
The diff algorithm:
The diff produces terminal escape sequences:
Key behaviors:
The Screen is the pixel buffer — a packed Int32Array:
This packed layout halves memory accesses and enables future SIMD comparison.
Interns ANSI style arrays into integer IDs:
Features:
transition(fromId, toId)) for zero-allocation ANSI stringsInterns character strings with an ASCII fast-path:
<Box>The fundamental layout primitive — equivalent to <div style="display:flex">:
Supports: flex direction, grow, shrink, wrap, margins, padding, gaps, overflow, event handlers (click, focus, hover, keydown).
<Text>Displays styled text:
Supports: color, background, bold, dim, italic, underline, strikethrough, inverse, text wrapping modes.
<ScrollBox>A Box with overflow scroll and imperative scroll API:
Features: viewport culling (only renders visible children), sticky scroll (auto-follow), scrollTo/scrollBy/scrollToBottom.
| Component | Purpose |
|---|---|
<Button> | Clickable button with state management |
<Link> | Clickable link (OSC 8 hyperlinks) |
<AlternateScreen> | Fullscreen mode (alternate screen buffer) |
<NoSelect> | Non-selectable region (gutters, line numbers) |
| Component | Purpose |
|---|---|
<Spacer> | Flexible spacing |
<Newline> | Insert newline(s) |
<RawAnsi> | Render raw ANSI escape sequences |
<ErrorOverview> | Error boundary display |
<App>)The root component that wraps everything:
The <App> component parses keyboard input:
Special handling:
Mouse events are tracked in <AlternateScreen> mode:
Rendering is throttled to ~60fps (16ms frame interval):
Shared resource pools avoid per-frame allocations:
StylePool — interned style IDsCharPool — interned character IDsHyperlinkPool — interned hyperlink IDs<ScrollBox> only renders children in the visible window, not the entire scrollable content.
The <AlternateScreen> component:
ink.ts)| File | Purpose |
|---|---|
src/ink.ts | Public API (render, createRoot, exports) |
src/ink/ink.tsx | Ink class — central orchestrator |
src/ink/reconciler.ts | React reconciler host config |
src/ink/renderer.ts | Tree walk → Output buffer |
src/ink/output.ts | Operation recorder (write, blit, clear, clip) |
src/ink/screen.ts | Pixel buffer (Int32Array), StylePool, CharPool |
src/ink/log-update.ts | Diff → terminal escape sequences |
src/ink/root.ts | Root component management |
src/ink/components/App.tsx | Root app component (input, suspend, mouse) |
src/ink/components/Box.tsx | Layout primitive |
src/ink/components/Text.tsx | Text display |
src/ink/components/ScrollBox.tsx | Scrollable container |
src/ink/components/Button.tsx | Interactive button |
src/ink/components/AlternateScreen.tsx | Fullscreen mode |