Files
claude-code/docs/ink-guide.md
2026-04-22 13:33:02 +08:00

29 KiB
Raw Blame History

@anthropic/ink 使用文档

本项目自定义的终端 React 渲染框架,基于 React 19 + react-reconciler + Yoga 布局引擎。 不是 npm 上的 ink 官方库API 有大量扩展和差异。


目录

  1. 架构概览
  2. 快速开始
  3. 渲染 API
  4. 组件体系
  5. 布局系统 (Styles)
  6. Hooks
  7. 快捷键系统
  8. 主题系统
  9. 事件系统
  10. 工具函数
  11. 与官方 Ink 的关键差异
  12. 最佳实践

1. 架构概览

packages/@ant/ink/src/
├── core/              # Layer 1: 渲染引擎
│   ├── events/        # 事件系统 (InputEvent, ClickEvent, FocusEvent...)
│   ├── layout/        # Yoga flexbox 布局
│   ├── termio/        # 终端 I/O (ANSI 解析/输出)
│   ├── renderer.ts    # 渲染器 (双缓冲 + diff)
│   ├── reconciler.ts  # React reconciler
│   └── styles.ts      # 样式类型定义
├── components/        # Layer 2: UI 基础组件 (无主题)
│   ├── Box.tsx        # BaseBox
│   ├── Text.tsx       # BaseText
│   ├── ScrollBox.tsx  # 滚动容器
│   ├── Button.tsx     # 按钮
│   └── ...
├── theme/             # Layer 3: 主题 + 设计系统
│   ├── ThemeProvider.tsx
│   ├── ThemedBox.tsx  # 主题感知的 Box
│   ├── ThemedText.tsx # 主题感知的 Text
│   ├── Dialog.tsx     # 对话框
│   ├── Tabs.tsx       # 标签页
│   └── ...
├── hooks/             # React hooks
├── keybindings/       # 快捷键系统
└── index.ts           # 统一导出

三层架构:

  • core — React reconciler + Yoga 布局 + 双缓冲渲染 + 终端 I/O
  • components — 原始 UI 组件 (无主题色,只接受 raw Color)
  • theme — 主题感知组件 + 设计系统 (接受 Theme key 作为颜色)

2. 快速开始

import {
  wrappedRender as render,
  Box,
  Text,
  useApp,
  useInput,
} from '@anthropic/ink'

function App() {
  const { exit } = useApp()

  useInput((input, key) => {
    if (input === 'q') exit()
  })

  return (
    <Box flexDirection="column" padding={1}>
      <Text color="claude" bold>Hello Terminal!</Text>
      <Text dimColor> q 退出</Text>
    </Box>
  )
}

// 渲染
const { unmount, waitUntilExit } = render(<App />)
await waitUntilExit()

注意: 项目中统一使用 wrappedRender(导出时别名为 render),而非 renderSync


3. 渲染 API

wrappedRender(node, options?)

异步渲染,返回 Promise<Instance>

type Instance = {
  rerender: (node: ReactNode) => void  // 重新渲染
  unmount: () => void                   // 卸载
  waitUntilExit: () => Promise<void>    // 等待退出
  cleanup: () => void                   // 清理
}

type RenderOptions = {
  stdout?: NodeJS.WriteStream    // 默认 process.stdout
  stdin?: NodeJS.ReadStream      // 默认 process.stdin
  stderr?: NodeJS.WriteStream    // 默认 process.stderr
  exitOnCtrlC?: boolean          // 默认 true
  patchConsole?: boolean         // 默认 true
  onFrame?: (event: FrameEvent) => void
}

createRoot(options?)

创建可复用的渲染根(类似 react-dom 的 createRoot

type Root = {
  render: (node: ReactNode) => void
  unmount: () => void
  waitUntilExit: () => Promise<void>
}

const root = createRoot({ stdout: process.stdout })
root.render(<App />)
// 后续可多次调用 root.render() 切换界面

4. 组件体系

双层组件设计

本框架的组件分两层:

层级 导出名 颜色类型 用途
Base BaseBox, BaseText Color (rgb/hex/ansi) 底层组件,无主题依赖
Themed Box, Text keyof Theme | Color 日常使用的组件,自动解析主题色

日常开发请始终使用 Box / TextThemed 版本),它们是默认导出。


Box / ThemedBox

终端里的 <div style="display: flex">,是最核心的布局容器。

import { Box, Text } from '@anthropic/ink'

// 基础布局
<Box flexDirection="column" gap={1}>
  <Box flexDirection="row" justifyContent="space-between">
    <Text>左侧</Text>
    <Text>右侧</Text>
  </Box>
</Box>

// 主题色边框
<Box borderStyle="round" borderColor="permission" padding={1}>
  <Text color="claude">带主题色边框的盒子</Text>
</Box>

// 键盘交互
<Box
  tabIndex={0}
  autoFocus
  onKeyDown={(e) => {
    if (e.key === 'escape') e.preventDefault()
  }}
>
  <Text>可聚焦的盒子</Text>
</Box>

Props:

属性 类型 说明
ref Ref<DOMElement> 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 属性 布局系统

ThemedBox 额外的颜色属性:

属性 类型 说明
borderColor keyof Theme | Color 边框颜色(接受主题 key
borderTopColor 同上 单边边框颜色
backgroundColor keyof Theme | Color 背景颜色(接受主题 key

Text / ThemedText

文本渲染组件。

import { Text } from '@anthropic/ink'

// 主题色文本
<Text color="claude" bold>Claude</Text>
<Text color="error">错误信息</Text>
<Text dimColor>次要信息(使用 inactive 色)</Text>

// 原始色值
<Text color="rgb(255,128,0)">橙色</Text>
<Text color="#ff8000">也是橙色</Text>
<Text color="ansi256(208)">256色橙色</Text>

// 文本截断
<Text wrap="truncate-end">很长的文本会被截断...</Text>

// bold 和 dim 互斥(终端限制)
<Text bold>加粗文本</Text>
<Text dim>暗淡文本</Text>
// ❌ <Text bold dim>不允许</Text>

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 提供级联颜色。

import { Text, TextHoverColorContext } from '@anthropic/ink'

<TextHoverColorContext.Provider value="suggestion">
  <Text>这段文字会继承 suggestion </Text>
  <Text color="error">这段保持 error </Text>
</TextHoverColorContext.Provider>

ScrollBox

支持虚拟滚动的容器,用于显示超出视口的内容。

import { ScrollBox, Box, Text } from '@anthropic/ink'
import { useRef } from 'react'

function LogViewer({ lines }: { lines: string[] }) {
  const scrollRef = useRef<ScrollBoxHandle>(null)

  // 编程式滚动
  useKeybinding('app:scrollUp', () => scrollRef.current?.scrollBy(-5))
  useKeybinding('app:scrollDown', () => scrollRef.current?.scrollBy(5))

  return (
    <ScrollBox ref={scrollRef} flexDirection="column" flexGrow={1} stickyScroll>
      {lines.map((line, i) => (
        <Text key={i}>{line}</Text>
      ))}
    </ScrollBox>
  )
}

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和鼠标点击。

import { Button, Text } from '@anthropic/ink'

function ConfirmButton({ onConfirm }: { onConfirm: () => void }) {
  return (
    <Button onAction={onConfirm} tabIndex={0}>
      {({ focused, hovered }) => (
        <Box paddingX={2} borderStyle="round"
             borderColor={focused ? 'claude' : 'inactive'}>
          <Text bold={focused}>
            {hovered ? '[ 确认 ]' : '确认'}
          </Text>
        </Box>
      )}
    </Button>
  )
}

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

<Newline />       // 插入一个空行
<Newline count={3} />  // 插入三个空行

Spacer

<Box flexDirection="row">
  <Text></Text>
  <Spacer />    // 占据剩余空间
  <Text></Text>
</Box>
<Link url="https://example.com">点击打开</Link>

NoSelect — 禁止文本选择

<NoSelect fromLeftEdge>
  <Text>这段文字无法被鼠标选中复制</Text>
</NoSelect>

RawAnsi — 预渲染 ANSI 内容

<RawAnsi content={ansiString} height={10} width={80} />

AlternateScreen — 全屏 TUI 模式

<AlternateScreen>
  {/* 启用鼠标追踪、文本选择等高级功能 */}
  <App />
</AlternateScreen>

设计系统组件

这些是 theme 层提供的高级 UI 组件。

Dialog — 对话框

import { Dialog, Box, Text } from '@anthropic/ink'

<Dialog title="确认操作" color="warning" onCancel={handleCancel}>
  <Box flexDirection="column" gap={1}>
    <Text>确定要继续吗?</Text>
  </Box>
  <Select options={options} onChange={onChange} onCancel={handleCancel} />
</Dialog>

Props: title, subtitle?, color? (Theme key), onCancel, hideInputGuide?, hideBorder?

Tabs / Tab — 标签页

import { Tabs, Tab, Box, Text } from '@anthropic/ink'

<Tabs title="设置" color="claude" defaultTab="general">
  <Tab id="general" label="通用">
    <Text>通用设置内容</Text>
  </Tab>
  <Tab id="advanced" label="高级">
    <Text>高级设置内容</Text>
  </Tab>
</Tabs>

Tabs Props: title?, color?, defaultTab?, selectedTab?, onTabChange?, banner?, contentHeight?, navFromContent?

Pane — 带边框的容器

<Pane title="日志" borderColor="bashBorder">
  <Text>内容</Text>
</Pane>

Divider — 分隔线

<Divider />
<Divider title="分隔区域" />

ProgressBar — 进度条

<ProgressBar value={75} maxValue={100} />

StatusIcon — 状态图标

<StatusIcon kind="success" />  // ✓
<StatusIcon kind="error" />    // ✗
<StatusIcon kind="warning" />
<StatusIcon kind="info" />

FuzzyPicker — 模糊搜索选择器

<FuzzyPicker
  items={[{ label: 'Item 1', value: '1' }, ...]}
  onSelect={(item) => handleSelect(item)}
  onCancel={() => {}}
/>

SearchBox — 搜索框

<SearchBox value={query} onChange={setQuery} placeholder="搜索..." />

ListItem — 列表项

<ListItem isFocused={focusedIndex === i} isSelected={selected === i}>
  <Text>{item.label}</Text>
</ListItem>

Spinner — 加载动画

<Spinner label="加载中..." />

LoadingState — 加载状态

<LoadingState message="处理中,请稍候..." />

Byline — 提示信息行

<Byline>
  <Text> Tab 切换</Text>
  <Text> Enter 确认</Text>
</Byline>

KeyboardShortcutHint / ConfigurableShortcutHint — 快捷键提示

<KeyboardShortcutHint shortcut="ctrl+k" description="清除" />

5. 布局系统 (Styles)

所有布局属性都通过 Box 的 props 传入,底层使用 Yoga 引擎计算 flexbox 布局。

Flexbox

<Box
  flexDirection="column"    // 'row' | 'column' | 'row-reverse' | 'column-reverse'
  flexWrap="wrap"           // 'nowrap' | 'wrap' | 'wrap-reverse'
  flexGrow={1}              // 弹性增长
  flexShrink={0}            // 弹性收缩(默认 1
  flexBasis={100}           // 初始尺寸
  alignItems="center"       // 'flex-start' | 'center' | 'flex-end' | 'stretch'
  alignSelf="flex-start"    // 'flex-start' | 'center' | 'flex-end' | 'auto'
  justifyContent="space-between"  // 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'
  gap={1}                   // 行列间距
  columnGap={2}             // 列间距
  rowGap={1}                // 行间距
>

尺寸

<Box
  width={50}          // 固定宽度(字符数)
  width="50%"         // 百分比宽度
  height={20}         // 固定高度(行数)
  minWidth={10}
  maxWidth={100}
  minHeight={5}
  maxHeight={50}
>

间距

<Box
  margin={1}          // 四周外边距
  marginX={2}         // 水平外边距
  marginY={1}         // 垂直外边距
  marginTop={1}       // 上外边距
  padding={1}         // 四周内边距
  paddingX={2}        // 水平内边距
  paddingY={1}        // 垂直内边距
  paddingLeft={2}     // 左内边距
>

重要: spacing 值必须是整数,否则会触发警告。

定位

<Box position="absolute" top={0} right={0}>
  <Text>绝对定位在右上角</Text>
</Box>

<Box position="relative" top={1} left={2}>
  <Text>相对偏移</Text>
</Box>

边框

<Box
  borderStyle="round"     // 'round' | 'single' | 'double' | 'bold' | 'dashed' | ...
  borderColor="claude"    // ThemedBox 可用主题 key
  borderTop={false}       // 隐藏上边框
  borderDimColor          // 暗淡边框
  borderText={{ text: '标题', side: 'top', alignment: 'center' }}
>

borderStyle 可选值: round, single, double, bold, dashed, 以及 cli-boxes 支持的所有样式。

溢出与滚动

<Box
  overflow="hidden"       // 'visible' | 'hidden' | 'scroll'
  overflowY="scroll"      // 垂直方向滚动
  overflowX="hidden"      // 水平方向隐藏
>

其他

<Box
  display="none"          // 隐藏元素('flex' | 'none'
  opaque                  // 用空格填充内部(遮挡背后内容)
  noSelect                // 禁止文本选择
  noSelect="from-left-edge"  // 从行首到右边缘全部禁止选择
>

6. Hooks

useApp() — 应用控制

const { exit } = useApp()
// 调用 exit() 卸载应用

useInput(handler, options?) — 键盘输入

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 对象:

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() — 访问输入流

const { stdin, setRawMode, isRawModeSupported, internal_eventEmitter } = useStdin()

useTerminalSize() — 终端尺寸

const { columns, rows } = useTerminalSize()

useTheme() — 主题

const [themeName, setTheme] = useTheme()
// themeName: 'dark' | 'light' | ...
// setTheme('light') 或 setTheme('auto')

useThemeSetting() — 原始主题设置

const setting = useThemeSetting()
// 可能返回 'auto'(而 useTheme 返回解析后的实际值)

useKeybinding(action, handler, options?) — 单个快捷键

useKeybinding('chat:submit', () => {
  handleSubmit()
}, { context: 'Chat', isActive: !isDisabled })

useKeybindings(handlers, options?) — 多个快捷键

useKeybindings({
  'chat:submit': () => handleSubmit(),
  'chat:cancel': () => handleCancel(),
  'chat:undo': () => handleUndo(),
}, { context: 'Chat' })

useRegisterKeybindingContext(name) — 注册快捷键上下文

// 组件挂载时激活此上下文,使对应上下文的快捷键优先于 Global
useRegisterKeybindingContext('ThemePicker')

useTerminalFocus() — 终端窗口焦点

const isFocused = useTerminalFocus()
// 需要 DECSET 1004 支持

useTerminalTitle(title) — 设置终端标题

useTerminalTitle('Claude Code')

useSearchHighlight() — 搜索高亮

const { setQuery, scanElement, setPositions } = useSearchHighlight()

useSelection() / useHasSelection() — 文本选择

const { hasSelection, selectedText, copy, clear } = useSelection()
// 仅 AlternateScreen 内有效

useTerminalViewport() — 视口可见性

const [ref, entry] = useTerminalViewport()
// entry.isVisible: 元素是否在视口内

useDeclaredCursor() — IME 光标定位

const declaredRef = useDeclaredCursor()
// 用于 CJK 输入法的原生光标定位

useTerminalNotification() — 终端通知

const notify = useTerminalNotification()
notify({ title: '完成', body: '任务已完成' })

useExitOnCtrlCD() — 双击退出

const exitState = useExitOnCtrlCD()
// exitState.pending: boolean — 第一次按下后等待确认
// exitState.keyName: 'Ctrl+C' | 'Ctrl+D'

useAnimationFrame(callback) — 动画帧

useAnimationFrame((deltaTime) => {
  // 每帧调用
  setFrame(f => f + deltaTime)
})

useInterval(callback, delay) — 定时器

useInterval(() => {
  setCount(c => c + 1)
}, 1000)

useTimeout(callback, delay) — 延迟执行

useTimeout(() => {
  setLoading(false)
}, 3000)

useMinDisplayTime(ms, onComplete) — 最小显示时间

useMinDisplayTime(1000, () => {
  // 确保内容至少显示 1 秒
  goToNextStep()
})

useDoublePress() — 双击检测

const doublePress = useDoublePress()
doublePress('q', () => {
  // 快速按两次 q 触发
  exit()
})

useTabStatus(kind, title?) — iTerm2 标签状态

useTabStatus({ kind: 'success', title: 'Build Done' })
// kind: 'success' | 'error' | 'running'

7. 快捷键系统

架构

用户按键 → useInput → EventEmitter → ChordInterceptor (全局拦截)
                                            ↓
                                     KeybindingResolver (上下文匹配)
                                            ↓
                                     Handler 注册表 → 组件 Handler

定义格式

快捷键配置在 ~/.claude/keybindings.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

function ChatInput() {
  // 注册上下文(覆盖 Global 中相同的按键)
  useRegisterKeybindingContext('Chat')

  // 注册多个 handler
  useKeybindings({
    'chat:submit': () => handleSubmit(),
    'chat:cancel': () => handleCancel(),
  }, { context: 'Chat' })
}

显示快捷键

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 增/删

使用主题色

// 组件上直接使用主题 key
<Text color="claude" bold>Claude</Text>
<Box borderColor="permission" backgroundColor="background">

// 混合使用:主题 key + 原始色值
<Text color="claude">主题色</Text>
<Text color="rgb(255,0,0)">原始色</Text>

// color() 函数用于非组件场景
import { color } from '@anthropic/ink'
const [theme] = useTheme()
const text = color('error', theme)('出错了')

ThemeProvider

<ThemeProvider initialState="auto">
  <App />
</ThemeProvider>

Hooks:

  • useTheme()[ThemeName, (setting: ThemeSetting) => void]
  • useThemeSetting() — 原始设置(含 'auto'
  • usePreviewTheme(){ setPreviewTheme, savePreview, cancelPreview }

9. 事件系统

事件类型

事件类 说明
InputEvent 键盘输入
ClickEvent 鼠标点击
KeyboardEvent 键盘事件DOM 风格)
FocusEvent 焦点变化
TerminalFocusEvent 终端窗口焦点
PasteEvent 粘贴

事件传播

事件支持冒泡和 stopImmediatePropagation():

// Box 事件冒泡
<Box onClick={(e) => {
  // 从最深层 Box 冒泡上来
  e.stopImmediatePropagation()  // 阻止继续冒泡
}}>

EventEmitter

const emitter = new EventEmitter()
emitter.on('input', handler)
emitter.emit('input', event)

注册顺序很重要:先注册的 handler 先收到事件,可以调用 stopImmediatePropagation() 阻止后续 handler。


10. 工具函数

stringWidth(text) — 计算文本显示宽度

import { stringWidth } from '@anthropic/ink'

stringWidth('你好')    // 4中文字符占 2 列)
stringWidth('abc')     // 3
stringWidth('\x1b[31mred\x1b[0m')  // 3忽略 ANSI 转义)

measureElement(domElement) — 测量元素尺寸

import { measureElement } from '@anthropic/ink'

const { width, height } = measureElement(ref.current)

color(themeKey, theme) — 创建主题色着色函数

import { color } from '@anthropic/ink'
const [theme] = useTheme()

const red = color('error', theme)('错误文本')
const green = color('success', theme)(figures.tick)

colorize(text, color, backgroundColor?) — 原始色着色

import { colorize } from '@anthropic/ink'

colorize('Hello', 'rgb(255,0,0)')
colorize('Hello', '#ff0000')
colorize('Hello', 'ansi256(196)')

setClipboard(text) — 复制到剪贴板

import { setClipboard } from '@anthropic/ink'
setClipboard('复制的文本')  // 通过 OSC 52

Ansi 组件 — 渲染 ANSI 字符串

import { Ansi } from '@anthropic/ink'

<Ansi dimColor>{ansiColoredText}</Ansi>

wrapText(text, width, options?) — 文本换行

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 组件

// ✅ 推荐 — 使用主题色
import { Box, Text } from '@anthropic/ink'
<Text color="claude">主题色</Text>

// ❌ 避免 — 只有在明确不需要主题时使用
import { BaseBox, BaseText } from '@anthropic/ink'
<BaseText color="rgb(215,119,87)">原始色</BaseText>

2. 用 useKeybindings 代替直接 useInput

// ✅ 推荐 — 声明式,支持上下文切换
useKeybindings({
  'chat:submit': handleSubmit,
  'chat:cancel': handleCancel,
}, { context: 'Chat' })

// ❌ 避免 — 手动解析按键
useInput((input, key) => {
  if (key.return) handleSubmit()
  if (key.escape) handleCancel()
})

3. spacing 值必须是整数

// ✅
<Box margin={1} padding={2}>

// ❌ 触发运行时警告
<Box margin={1.5}>

4. bolddim 互斥

// ✅ 二选一
<Text bold>加粗</Text>
<Text dim>暗淡</Text>

// ❌ TypeScript 编译错误
<Text bold dim>...</Text>

5. 需要暗淡但不互斥时用 dimColor

// ✅ dimColor 与 bold 兼容(使用 inactive 色,不是 ANSI dim
<Text bold dimColor>加粗但暗淡</Text>

6. 鼠标事件只在 AlternateScreen 内生效

// onClick、onMouseEnter、onMouseLeave 需要在 AlternateScreen 内
<AlternateScreen>
  <Box onClick={handleClick}>可点击</Box>
</AlternateScreen>

7. ScrollBox 需要明确高度

// ✅ 设置 flexGrow 或固定 height
<ScrollBox flexGrow={1} stickyScroll>
  {content}
</ScrollBox>

// ❌ 没有高度约束ScrollBox 不知道视口大小
<ScrollBox>
  {content}
</ScrollBox>

8. 渲染入口模式

// 推荐wrappedRender异步用于独立界面
import { wrappedRender as render } from '@anthropic/ink'
const { unmount } = await render(<App />)

// 复用根createRoot
import { createRoot } from '@anthropic/ink'
const root = createRoot()
root.render(<Screen1 />)
// 后续切换
root.render(<Screen2 />)