关于 ink
@json-render/ink 将 JSON 规范渲染为交互式终端组件树,适用于构建由数据驱动的终端界面。它提供标准组件、状态绑定、条件显示、事件动作和流式渲染能力。
主要功能
- 01JSON 规范渲染为终端界面
- 02提供布局与内容组件
- 03输入、选择和确认交互
- 04状态绑定与条件显示
- 05内置状态操作与动态列表
- 06支持 JSONL 流式渲染
适用场景
- 01依据 JSON 规范生成终端界面
- 02构建带输入和选择控件的表单
- 03按状态显示或更新列表
- 04逐步呈现 API 流式界面规范
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ google-labs-code/ design.md --skill ink安装后在 Claude Code 中输入/ink确认已加载。
详细介绍
@json-render/ink
Ink 终端渲染器,可将 JSON 规范转换为交互式终端组件树,支持标准组件、数据绑定、可见性、动作和动态属性。
快速开始
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/ink/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/ink/catalog";
import { defineRegistry, Renderer, type Components } from "@json-render/ink";
import { z } from "zod";
// Create catalog with standard + custom components
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "Custom widget",
},
},
actions: standardActionDefinitions,
});
// Register only custom components (standard ones are built-in)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// Render
function App({ spec }) {
return (
<JSONUIProvider initialState={{}}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>
);
}
规范结构(扁平元素映射)
Ink 模式使用带根键的扁平元素映射:
{
"root": "main",
"elements": {
"main": {
"type": "Box",
"props": { "flexDirection": "column", "padding": 1 },
"children": ["heading", "content"]
},
"heading": {
"type": "Heading",
"props": { "text": "Dashboard", "level": "h1" },
"children": []
},
"content": {
"type": "Text",
"props": { "text": "Hello from the terminal!" },
"children": []
}
}
}
标准组件
布局
Box- Flexbox 布局容器(类似终端中的<div>)。用于分组、间距、边框和对齐。默认 flexDirection 为 row。Text- 带可选样式(颜色、粗体、斜体等)的文本输出Newline- 插入空行。必须位于 flexDirection 为 column 的 Box 内。Spacer- 沿主轴方向扩展的弹性空白区域。
内容
Heading- 章节标题(h1:粗体+下划线,h2:粗体,h3:粗体+暗淡,h4:暗淡)Divider- 带可选居中标题的水平分隔线Badge- 彩色行内标签(变体:default、info、success、warning、error)Spinner- 带可选标签的动画加载指示器ProgressBar- 水平进度条(0-1)Sparkline- 使用 Unicode 块字符的行内图表BarChart- 带标签和数值的水平柱状图Table- 带表头和行的表格数据List- 无序或有序列表ListItem- 包含标题、副标题、前导/尾随文本的结构化列表行Card- 带可选标题的边框容器KeyValue- 键值对展示Link- 带可选标签的可点击 URLStatusLine- 带彩色图标的状态消息(info、success、warning、error)Markdown- 以终端样式渲染 Markdown 文本
交互
TextInput- 文本输入框(事件:submit、change)Select- 支持方向键导航的选择菜单(事件:change)MultiSelect- 使用空格键切换的多选(事件:change、submit)ConfirmInput- 是/否确认提示(事件:confirm、deny)Tabs- 支持左右方向键导航的标签栏(事件:change)
可见性条件
在元素上使用 visible 可根据状态显示或隐藏。语法:{ "$state": "/path" }、{ "$state": "/path", "eq": value }、{ "$state": "/path", "not": true },{ "$and": [cond1, cond2] } 表示与,{ "$or": [cond1, cond2] } 表示或。
动态属性表达式
任何属性值都可以是在渲染时解析的数据驱动表达式:
{ "$state": "/state/key" }- 从状态模型读取(单向读取){ "$bindState": "/path" }- 双向绑定:用于表单组件的自然值属性{ "$bindItem": "field" }- 双向绑定到重复项的字段{ "$cond": <condition>, "$then": <value>, "$else": <value> }- 条件值{ "$template": "Hello, ${/name}!" }- 将状态值插入字符串
组件不使用 statePath 属性进行双向绑定。请在自然值属性上使用 { "$bindState": "/path" }。
事件系统
组件使用 emit 触发命名事件。元素的 on 字段将事件映射到动作绑定:
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") triggers the action bound in the spec's on.press */}
</Box>
),
{
"type": "CustomButton",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } },
"children": []
}
内置动作
setState、pushState 和 removeState 为内置动作,会被自动处理:
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
重复(动态列表)
在容器元素上使用 repeat 字段,可从状态数组渲染项目:
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}
在重复的子元素中,使用 { "$item": "field" } 读取当前项,使用 { "$index": true } 获取当前索引。
流式渲染
使用 useUIStream 从 JSONL 补丁流中逐步渲染规范:
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });
服务端提示词生成
使用 ./server 导出从你的目录生成 AI 系统提示词:
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });
Providers
| Provider | Purpose |
|---|---|
StateProvider | 在组件之间共享状态(使用 JSON Pointer 路径)。接受可选的 store 属性以启用受控模式。 |
ActionProvider | 处理通过事件系统分发的操作 |
VisibilityProvider | 基于状态启用条件渲染 |
ValidationProvider | 表单字段验证 |
FocusProvider | 管理交互组件之间的焦点 |
JSONUIProvider | 组合所有上下文的 Provider |
外部 Store(受控模式)
将 StateStore 传递给 StateProvider(或 JSONUIProvider),即可使用外部状态管理:
import { createStateStore, type StateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>{children}</StateProvider>
store.set("/count", 1); // React re-renders automatically
提供 store 时,initialState 和 onStateChange 会被忽略。
createRenderer(高层 API)
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// custom component overrides here
});
// InkRenderer includes all providers (state, visibility, actions, focus)
render(
<InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);
主要导出
| 导出 | 用途 |
|---|---|
defineRegistry | 基于 catalog 创建类型安全的组件注册表 |
Renderer | 使用注册表渲染 spec |
createRenderer | 高层 API:创建带有内置 Provider 的组件 |
JSONUIProvider | 组合所有上下文的 Provider |
schema | Ink 扁平元素映射的 schema(包含内置的状态操作) |
standardComponentDefinitions | 所有标准组件的 catalog 定义 |
standardActionDefinitions | 标准操作的 catalog 定义 |
standardComponents | 预构建的组件实现 |
useStateStore | 访问状态上下文 |
useStateValue | 获取状态中的单个值 |
useBoundProp | 为 $bindState/$bindItem 表达式提供双向绑定 |
useActions | 访问操作上下文 |
useAction | 获取单个操作分发函数 |
useOptionalValidation | useValidation 的非抛错变体 |
useUIStream | 从 API 端点流式接收 spec |
createStateStore | 创建与框架无关的内存 StateStore |
StateStore | 用于接入外部状态管理的接口 |
Components | 带类型的组件映射(感知 catalog) |
Actions | 带类型的操作映射(感知 catalog) |
ComponentContext | 带类型的组件上下文(感知 catalog) |
flatToTree | 将扁平元素映射转换为树结构 |
终端 UI 设计指南
- 使用 Box 进行布局(flexDirection、padding、gap)。默认 flexDirection 为 row。
- 终端宽度约为 80-120 列。主结构优先使用纵向布局(flexDirection: column)。
- 在 Box 上使用 borderStyle 进行视觉分组(single、double、round、bold)。
- 使用具名终端颜色:red、green、yellow、blue、magenta、cyan、white、gray。
- 使用 Heading 作为分区标题,Divider 分隔各分区,Badge 显示状态,KeyValue 显示带标签的数据,Card 用于带边框的分组。
- 在多视图 UI 中使用 Tabs,并为子内容设置 visible 条件。
- 使用 Sparkline 展示行内趋势,使用 BarChart 比较数值。