`,是最核心的布局容器。
```tsx
import { Box, Text } from '@anthropic/ink'
// 基础布局
左侧
右侧
// 主题色边框
带主题色边框的盒子
// 键盘交互
{
if (e.key === 'escape') e.preventDefault()
}}
>
可聚焦的盒子
```
**Props**:
| 属性 | 类型 | 说明 |
|------|------|------|
| `ref` | `Ref
` | DOM 元素引用 |
| `tabIndex` | `number` | Tab 键序(≥0 参与循环,-1 仅程序聚焦) |
| `autoFocus` | `boolean` | 挂载时自动聚焦 |
| `onClick` | `(e: ClickEvent) => void` | 鼠标点击(仅 AlternateScreen 内生效) |
| `onFocus` / `onBlur` | `(e: FocusEvent) => void` | 焦点事件 |
| `onFocusCapture` / `onBlurCapture` | 同上 | 捕获阶段焦点事件 |
| `onKeyDown` / `onKeyDownCapture` | `(e: KeyboardEvent) => void` | 键盘事件 |
| `onMouseEnter` / `onMouseLeave` | `() => void` | 鼠标进出(仅 AlternateScreen) |
| 所有 `Styles` 属性 | 见 [布局系统](#5-布局系统-styles) | |
**ThemedBox 额外的颜色属性**:
| 属性 | 类型 | 说明 |
|------|------|------|
| `borderColor` | `keyof Theme \| Color` | 边框颜色(接受主题 key) |
| `borderTopColor` 等 | 同上 | 单边边框颜色 |
| `backgroundColor` | `keyof Theme \| Color` | 背景颜色(接受主题 key) |
---
### Text / ThemedText
文本渲染组件。
```tsx
import { Text } from '@anthropic/ink'
// 主题色文本
Claude
错误信息
次要信息(使用 inactive 色)
// 原始色值
橙色
也是橙色
256色橙色
// 文本截断
很长的文本会被截断...
// bold 和 dim 互斥(终端限制)
加粗文本
暗淡文本
// ❌ 不允许
```
**Props**:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `color` | `keyof Theme \| Color` | — | 文字颜色 |
| `backgroundColor` | `keyof Theme` | — | 背景色(仅接受主题 key) |
| `bold` | `boolean` | — | 加粗(与 dim 互斥) |
| `dim` | `boolean` | — | 暗淡(与 bold 互斥) |
| `dimColor` | `boolean` | — | 使用主题 inactive 色着色(不与 bold 互斥) |
| `italic` | `boolean` | — | 斜体 |
| `underline` | `boolean` | — | 下划线 |
| `strikethrough` | `boolean` | — | 删除线 |
| `inverse` | `boolean` | — | 反转前景/背景 |
| `wrap` | 见下方 | `'wrap'` | 文本换行/截断模式 |
**wrap 模式**:
| 值 | 说明 |
|----|------|
| `'wrap'` | 自动换行(默认) |
| `'wrap-trim'` | 换行并去除行尾空格 |
| `'end'` | 不换行,末尾省略 |
| `'middle'` | 不换行,中间省略 |
| `'truncate-end'` | 末尾截断 |
| `'truncate'` | 同 truncate-end |
| `'truncate-middle'` | 中间截断 |
| `'truncate-start'` | 开头截断 |
**`TextHoverColorContext`**: 为子树中未设置 `color` 的 Text 提供级联颜色。
```tsx
import { Text, TextHoverColorContext } from '@anthropic/ink'
这段文字会继承 suggestion 色
这段保持 error 色
```
---
### ScrollBox
支持虚拟滚动的容器,用于显示超出视口的内容。
```tsx
import { ScrollBox, Box, Text } from '@anthropic/ink'
import { useRef } from 'react'
function LogViewer({ lines }: { lines: string[] }) {
const scrollRef = useRef(null)
// 编程式滚动
useKeybinding('app:scrollUp', () => scrollRef.current?.scrollBy(-5))
useKeybinding('app:scrollDown', () => scrollRef.current?.scrollBy(5))
return (
{lines.map((line, i) => (
{line}
))}
)
}
```
**ScrollBoxHandle 方法**:
| 方法 | 返回 | 说明 |
|------|------|------|
| `scrollTo(y)` | `void` | 滚动到指定行 |
| `scrollBy(dy)` | `void` | 相对滚动 |
| `scrollToElement(el, offset?)` | `void` | 滚动到指定元素 |
| `scrollToBottom()` | `void` | 滚到底部 |
| `getScrollTop()` | `number` | 当前滚动位置 |
| `getScrollHeight()` | `number` | 内容总高度 |
| `getViewportHeight()` | `number` | 视口高度 |
| `isSticky()` | `boolean` | 是否粘在底部 |
| `setClampBounds(min, max)` | `void` | 限制滚动范围 |
| `subscribe(listener)` | `() => void` | 订阅滚动变化 |
**关键 Props**:
- `stickyScroll` — 自动跟踪最新内容(适合日志/聊天)
- 继承所有 `Styles` 属性(需设置 `flexGrow={1}` 或固定 `height`)
---
### Button
可交互按钮,支持键盘(Enter/Space)和鼠标点击。
```tsx
import { Button, Text } from '@anthropic/ink'
function ConfirmButton({ onConfirm }: { onConfirm: () => void }) {
return (
)
}
```
**Props**:
| 属性 | 类型 | 说明 |
|------|------|------|
| `onAction` | `() => void` | 按下 Enter/Space/点击时触发 |
| `tabIndex` | `number` | Tab 序号 |
| `autoFocus` | `boolean` | 自动聚焦 |
| `children` | `(state: ButtonState) => ReactNode \| ReactNode` | render prop 或静态内容 |
**ButtonState**: `{ focused: boolean, hovered: boolean, active: boolean }`
---
### 其他基础组件
#### `Newline`
```tsx
// 插入一个空行
// 插入三个空行
```
#### `Spacer`
```tsx
左
// 占据剩余空间
右
```
#### `Link` — OSC 8 超链接
```tsx
点击打开
```
#### `NoSelect` — 禁止文本选择
```tsx
这段文字无法被鼠标选中复制
```
#### `RawAnsi` — 预渲染 ANSI 内容
```tsx
```
#### `AlternateScreen` — 全屏 TUI 模式
```tsx
{/* 启用鼠标追踪、文本选择等高级功能 */}
```
---
### 设计系统组件
这些是 theme 层提供的高级 UI 组件。
#### `Dialog` — 对话框
```tsx
import { Dialog, Box, Text } from '@anthropic/ink'
```
**Props**: `title`, `subtitle?`, `color?` (Theme key), `onCancel`, `hideInputGuide?`, `hideBorder?`
#### `Tabs` / `Tab` — 标签页
```tsx
import { Tabs, Tab, Box, Text } from '@anthropic/ink'
通用设置内容
高级设置内容
```
**Tabs Props**: `title?`, `color?`, `defaultTab?`, `selectedTab?`, `onTabChange?`, `banner?`, `contentHeight?`, `navFromContent?`
#### `Pane` — 带边框的容器
```tsx
内容
```
#### `Divider` — 分隔线
```tsx
```
#### `ProgressBar` — 进度条
```tsx
```
#### `StatusIcon` — 状态图标
```tsx
// ✓
// ✗
```
#### `FuzzyPicker` — 模糊搜索选择器
```tsx
handleSelect(item)}
onCancel={() => {}}
/>
```
#### `SearchBox` — 搜索框
```tsx
```
#### `ListItem` — 列表项
```tsx
{item.label}
```
#### `Spinner` — 加载动画
```tsx
```
#### `LoadingState` — 加载状态
```tsx
```
#### `Byline` — 提示信息行
```tsx
按 Tab 切换
按 Enter 确认
```
#### `KeyboardShortcutHint` / `ConfigurableShortcutHint` — 快捷键提示
```tsx
```
---
## 5. 布局系统 (Styles)
所有布局属性都通过 `Box` 的 props 传入,底层使用 Yoga 引擎计算 flexbox 布局。
### Flexbox
```tsx
```
### 尺寸
```tsx
```
### 间距
```tsx
```
**重要**: spacing 值必须是整数,否则会触发警告。
### 定位
```tsx
绝对定位在右上角
相对偏移
```
### 边框
```tsx
```
**borderStyle 可选值**: `round`, `single`, `double`, `bold`, `dashed`, 以及 `cli-boxes` 支持的所有样式。
### 溢出与滚动
```tsx
```
### 其他
```tsx
```
---
## 6. Hooks
### `useApp()` — 应用控制
```tsx
const { exit } = useApp()
// 调用 exit() 卸载应用
```
### `useInput(handler, options?)` — 键盘输入
```tsx
useInput((input, key, event) => {
if (input === 'q') exit()
if (key.ctrl && input === 'c') handleInterrupt()
if (key.upArrow) moveUp()
if (key.return) submit()
}, { isActive: isFocused }) // isActive 控制是否启用
```
**Key 对象**:
```ts
type Key = {
upArrow: boolean
downArrow: boolean
leftArrow: boolean
rightArrow: boolean
return: boolean
escape: boolean
ctrl: boolean
shift: boolean
meta: boolean // Alt/Option
super_: boolean // Cmd/Win (仅 kitty 协议)
tab: boolean
backspace: boolean
delete: boolean
pageDown: boolean
pageUp: boolean
home: boolean
end: boolean
}
```
**重要**: `useInput` 注册顺序影响事件传播。先注册的 handler 可以调用 `event.stopImmediatePropagation()` 阻止后续 handler。
### `useStdin()` — 访问输入流
```tsx
const { stdin, setRawMode, isRawModeSupported, internal_eventEmitter } = useStdin()
```
### `useTerminalSize()` — 终端尺寸
```tsx
const { columns, rows } = useTerminalSize()
```
### `useTheme()` — 主题
```tsx
const [themeName, setTheme] = useTheme()
// themeName: 'dark' | 'light' | ...
// setTheme('light') 或 setTheme('auto')
```
### `useThemeSetting()` — 原始主题设置
```tsx
const setting = useThemeSetting()
// 可能返回 'auto'(而 useTheme 返回解析后的实际值)
```
### `useKeybinding(action, handler, options?)` — 单个快捷键
```tsx
useKeybinding('chat:submit', () => {
handleSubmit()
}, { context: 'Chat', isActive: !isDisabled })
```
### `useKeybindings(handlers, options?)` — 多个快捷键
```tsx
useKeybindings({
'chat:submit': () => handleSubmit(),
'chat:cancel': () => handleCancel(),
'chat:undo': () => handleUndo(),
}, { context: 'Chat' })
```
### `useRegisterKeybindingContext(name)` — 注册快捷键上下文
```tsx
// 组件挂载时激活此上下文,使对应上下文的快捷键优先于 Global
useRegisterKeybindingContext('ThemePicker')
```
### `useTerminalFocus()` — 终端窗口焦点
```tsx
const isFocused = useTerminalFocus()
// 需要 DECSET 1004 支持
```
### `useTerminalTitle(title)` — 设置终端标题
```tsx
useTerminalTitle('Claude Code')
```
### `useSearchHighlight()` — 搜索高亮
```tsx
const { setQuery, scanElement, setPositions } = useSearchHighlight()
```
### `useSelection()` / `useHasSelection()` — 文本选择
```tsx
const { hasSelection, selectedText, copy, clear } = useSelection()
// 仅 AlternateScreen 内有效
```
### `useTerminalViewport()` — 视口可见性
```tsx
const [ref, entry] = useTerminalViewport()
// entry.isVisible: 元素是否在视口内
```
### `useDeclaredCursor()` — IME 光标定位
```tsx
const declaredRef = useDeclaredCursor()
// 用于 CJK 输入法的原生光标定位
```
### `useTerminalNotification()` — 终端通知
```tsx
const notify = useTerminalNotification()
notify({ title: '完成', body: '任务已完成' })
```
### `useExitOnCtrlCD()` — 双击退出
```tsx
const exitState = useExitOnCtrlCD()
// exitState.pending: boolean — 第一次按下后等待确认
// exitState.keyName: 'Ctrl+C' | 'Ctrl+D'
```
### `useAnimationFrame(callback)` — 动画帧
```tsx
useAnimationFrame((deltaTime) => {
// 每帧调用
setFrame(f => f + deltaTime)
})
```
### `useInterval(callback, delay)` — 定时器
```tsx
useInterval(() => {
setCount(c => c + 1)
}, 1000)
```
### `useTimeout(callback, delay)` — 延迟执行
```tsx
useTimeout(() => {
setLoading(false)
}, 3000)
```
### `useMinDisplayTime(ms, onComplete)` — 最小显示时间
```tsx
useMinDisplayTime(1000, () => {
// 确保内容至少显示 1 秒
goToNextStep()
})
```
### `useDoublePress()` — 双击检测
```tsx
const doublePress = useDoublePress()
doublePress('q', () => {
// 快速按两次 q 触发
exit()
})
```
### `useTabStatus(kind, title?)` — iTerm2 标签状态
```tsx
useTabStatus({ kind: 'success', title: 'Build Done' })
// kind: 'success' | 'error' | 'running'
```
---
## 7. 快捷键系统
### 架构
```
用户按键 → useInput → EventEmitter → ChordInterceptor (全局拦截)
↓
KeybindingResolver (上下文匹配)
↓
Handler 注册表 → 组件 Handler
```
### 定义格式
快捷键配置在 `~/.claude/keybindings.json`:
```json
{
"bindings": [
{
"context": "Global",
"bindings": {
"ctrl+t": "app:toggleTodos",
"ctrl+o": "app:toggleTranscript"
}
},
{
"context": "Chat",
"bindings": {
"enter": "chat:submit",
"escape": "chat:cancel",
"ctrl+x ctrl+k": "chat:killAgents"
}
}
]
}
```
### 按键语法
**单键**:
- `ctrl+k`, `shift+tab`, `alt+v`, `cmd+c`
- `escape`, `enter`, `return`, `space`, `tab`, `backspace`, `delete`
- `up`, `down`, `left`, `right`(也支持 `↑` `↓` `←` `→`)
- `pageup`, `pagedown`, `home`, `end`
**修饰符别名**: `ctrl`/`control`, `alt`/`opt`/`option`, `cmd`/`command`/`super`/`win`
**Chord(组合序列)**: `ctrl+x ctrl+k` — 先按 `ctrl+x`,再按 `ctrl+k`
### 上下文系统
| 上下文 | 说明 |
|--------|------|
| `Global` | 全局生效 |
| `Chat` | 聊天输入聚焦时 |
| `Autocomplete` | 自动补全菜单显示时 |
| `Confirmation` | 确认对话框 |
| `Settings` | 设置菜单 |
| `Transcript` | 查看转录 |
| `ThemePicker` | 主题选择器 |
| `Select` | 选择组件 |
**优先级**: 注册的活动上下文 > 组件上下文 > `Global`
### Action 类型
- **内置 Action**: `app:toggleTodos`, `chat:submit` 等
- **命令 Action**: `command:help`, `command:commit` — 执行 slash 命令
- **解绑**: 设为 `null` — 取消默认绑定
### 注册 Handler
```tsx
function ChatInput() {
// 注册上下文(覆盖 Global 中相同的按键)
useRegisterKeybindingContext('Chat')
// 注册多个 handler
useKeybindings({
'chat:submit': () => handleSubmit(),
'chat:cancel': () => handleCancel(),
}, { context: 'Chat' })
}
```
### 显示快捷键
```tsx
import { useShortcutDisplay } from '@/keybindings/useShortcutDisplay'
const shortcut = useShortcutDisplay('app:toggleTranscript', 'Global', 'ctrl+o')
// 返回用户自定义的按键或 fallback
```
---
## 8. 主题系统
### 可用主题
| 名称 | 说明 |
|------|------|
| `dark` | 默认暗色 |
| `light` | 亮色 |
| `dark-daltonized` | 色盲友好暗色 |
| `light-daltonized` | 色盲友好亮色 |
| `dark-ansi` | 16色 ANSI 暗色 |
| `light-ansi` | 16色 ANSI 亮色 |
| `auto` | 跟随终端主题 |
### 常用 Theme 色值 key
| Key | 用途 |
|-----|------|
| `text` | 默认文字色 |
| `background` | 背景色 |
| `claude` | Claude 品牌色 |
| `claudeShimmer` | Claude 动画色 |
| `permission` | 权限相关 |
| `suggestion` | 建议/提示 |
| `success` | 成功 |
| `error` | 错误 |
| `warning` | 警告 |
| `inactive` | 非活跃/暗淡 |
| `bashBorder` | Bash 边框 |
| `diffAdded` / `diffRemoved` | Diff 增/删 |
### 使用主题色
```tsx
// 组件上直接使用主题 key
Claude
// 混合使用:主题 key + 原始色值
主题色
原始色
// color() 函数用于非组件场景
import { color } from '@anthropic/ink'
const [theme] = useTheme()
const text = color('error', theme)('出错了')
```
### ThemeProvider
```tsx
```
**Hooks**:
- `useTheme()` — `[ThemeName, (setting: ThemeSetting) => void]`
- `useThemeSetting()` — 原始设置(含 `'auto'`)
- `usePreviewTheme()` — `{ setPreviewTheme, savePreview, cancelPreview }`
---
## 9. 事件系统
### 事件类型
| 事件类 | 说明 |
|--------|------|
| `InputEvent` | 键盘输入 |
| `ClickEvent` | 鼠标点击 |
| `KeyboardEvent` | 键盘事件(DOM 风格) |
| `FocusEvent` | 焦点变化 |
| `TerminalFocusEvent` | 终端窗口焦点 |
| `PasteEvent` | 粘贴 |
### 事件传播
事件支持冒泡和 `stopImmediatePropagation()`:
```tsx
// Box 事件冒泡
{
// 从最深层 Box 冒泡上来
e.stopImmediatePropagation() // 阻止继续冒泡
}}>
```
### EventEmitter
```tsx
const emitter = new EventEmitter()
emitter.on('input', handler)
emitter.emit('input', event)
```
**注册顺序**很重要:先注册的 handler 先收到事件,可以调用 `stopImmediatePropagation()` 阻止后续 handler。
---
## 10. 工具函数
### `stringWidth(text)` — 计算文本显示宽度
```tsx
import { stringWidth } from '@anthropic/ink'
stringWidth('你好') // 4(中文字符占 2 列)
stringWidth('abc') // 3
stringWidth('\x1b[31mred\x1b[0m') // 3(忽略 ANSI 转义)
```
### `measureElement(domElement)` — 测量元素尺寸
```tsx
import { measureElement } from '@anthropic/ink'
const { width, height } = measureElement(ref.current)
```
### `color(themeKey, theme)` — 创建主题色着色函数
```tsx
import { color } from '@anthropic/ink'
const [theme] = useTheme()
const red = color('error', theme)('错误文本')
const green = color('success', theme)(figures.tick)
```
### `colorize(text, color, backgroundColor?)` — 原始色着色
```tsx
import { colorize } from '@anthropic/ink'
colorize('Hello', 'rgb(255,0,0)')
colorize('Hello', '#ff0000')
colorize('Hello', 'ansi256(196)')
```
### `setClipboard(text)` — 复制到剪贴板
```tsx
import { setClipboard } from '@anthropic/ink'
setClipboard('复制的文本') // 通过 OSC 52
```
### `Ansi` 组件 — 渲染 ANSI 字符串
```tsx
import { Ansi } from '@anthropic/ink'
{ansiColoredText}
```
### `wrapText(text, width, options?)` — 文本换行
```tsx
import wrapText from '@anthropic/ink'
const wrapped = wrapText('很长的文本...', 80)
```
---
## 11. 与官方 Ink 的关键差异
| 特性 | 官方 Ink | @anthropic/ink |
|------|----------|----------------|
| React 版本 | 18 | **19** |
| 渲染方式 | 单缓冲 | **双缓冲 + diff 优化** |
| 布局引擎 | Yoga 基础 | **Yoga + 定位 + overflow + 虚拟滚动** |
| 全屏模式 | 无 | **AlternateScreen + 鼠标追踪** |
| 文本选择 | 无 | **鼠标选择 + OSC 52 剪贴板** |
| 主题系统 | 无 | **多主题 + 自动检测 + 色盲友好** |
| 快捷键 | `useInput` only | **Chord + 上下文 + 热重载** |
| 设计系统 | 无 | **Dialog/Tabs/Pane/ProgressBar 等** |
| 事件系统 | 简单 | **DOM 风格冒泡 + capture 阶段** |
| CJK 输入 | 无支持 | **IME 光标定位** |
| 性能优化 | 无 | **Blit 优化 + damage tracking + pool** |
| 组件层级 | 单层 | **双层 (Base + Themed)** |
| 边框系统 | 基础 | **per-side 控制 + dimColor + borderText** |
| 文本换行 | `wrap` | **wrap/wrap-trim/end/middle/truncate-\*** |
---
## 12. 最佳实践
### 1. 始终使用 Themed 组件
```tsx
// ✅ 推荐 — 使用主题色
import { Box, Text } from '@anthropic/ink'
主题色
// ❌ 避免 — 只有在明确不需要主题时使用
import { BaseBox, BaseText } from '@anthropic/ink'
原始色
```
### 2. 用 `useKeybindings` 代替直接 `useInput`
```tsx
// ✅ 推荐 — 声明式,支持上下文切换
useKeybindings({
'chat:submit': handleSubmit,
'chat:cancel': handleCancel,
}, { context: 'Chat' })
// ❌ 避免 — 手动解析按键
useInput((input, key) => {
if (key.return) handleSubmit()
if (key.escape) handleCancel()
})
```
### 3. spacing 值必须是整数
```tsx
// ✅
// ❌ 触发运行时警告
```
### 4. `bold` 和 `dim` 互斥
```tsx
// ✅ 二选一
加粗
暗淡
// ❌ TypeScript 编译错误
...
```
### 5. 需要暗淡但不互斥时用 `dimColor`
```tsx
// ✅ dimColor 与 bold 兼容(使用 inactive 色,不是 ANSI dim)
加粗但暗淡
```
### 6. 鼠标事件只在 AlternateScreen 内生效
```tsx
// onClick、onMouseEnter、onMouseLeave 需要在 AlternateScreen 内
可点击
```
### 7. ScrollBox 需要明确高度
```tsx
// ✅ 设置 flexGrow 或固定 height
{content}
// ❌ 没有高度约束,ScrollBox 不知道视口大小
{content}
```
### 8. 渲染入口模式
```tsx
// 推荐:wrappedRender(异步,用于独立界面)
import { wrappedRender as render } from '@anthropic/ink'
const { unmount } = await render()
// 复用根:createRoot
import { createRoot } from '@anthropic/ink'
const root = createRoot()
root.render()
// 后续切换
root.render()
```