关于 ink
@json-render/ink 将 JSON 规格渲染为交互式终端组件树,面向终端界面开发者,结合标准组件、状态绑定、事件、操作和流式规格。
主要功能
- 01在终端渲染 JSON 规格
- 02提供标准布局与内容组件
- 03支持交互输入和导航
- 04支持状态绑定与条件显示
- 05流式处理 JSONL 规格补丁
适用场景
- 01根据 JSON 规格构建终端界面
- 02创建有状态的终端表单和菜单
- 03逐步渲染流式 UI 规格
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ json-render --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- 可点击的 URL,可选标签StatusLine- 带彩色图标的状态消息(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] } 表示 AND,{ "$or": [cond1, cond2] } 表示 OR。
动态属性表达式
任何属性值都可以是由数据驱动、在渲染时解析的表达式:
{ "$state": "/state/key" }- 从状态模型读取(单向读取){ "$bindState": "/path" }- 双向绑定:用于表单组件的自然 value 属性{ "$bindItem": "field" }- 双向绑定到重复项字段{ "$cond": <condition>, "$then": <value>, "$else": <value> }- 条件值{ "$template": "Hello, ${/name}!" }- 将状态值插入字符串
组件不使用 statePath 属性进行双向绑定。应在自然 value 属性上使用 { "$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 } 获取当前索引。
对于嵌套列表,内部 repeat 可以使用 { "statePath": { "$item": "children" } },遍历外层项目中的数组。
流式传输
使用 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 比较数值。