ink

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@json-render/ink

@json-render/ink

Ink terminal renderer that converts JSON specs into interactive terminal component trees with standard components, data binding, visibility, actions, and dynamic props.
这是一款Ink终端渲染器,可将JSON规格转换为包含标准组件、数据绑定、可见性控制、动作处理和动态属性的交互式终端组件树。

Quick Start

快速开始

typescript
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>
  );
}
typescript
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";

// 创建包含标准组件和自定义组件的目录
const catalog = defineCatalog(schema, {
  components: {
    ...standardComponentDefinitions,
    CustomWidget: {
      props: z.object({ title: z.string() }),
      slots: [],
      description: "Custom widget",
    },
  },
  actions: standardActionDefinitions,
});

// 仅注册自定义组件(标准组件已内置)
const { registry } = defineRegistry(catalog, {
  components: {
    CustomWidget: ({ props }) => <Text>{props.title}</Text>,
  } as Components<typeof catalog>,
});

// 渲染
function App({ spec }) {
  return (
    <JSONUIProvider initialState={{}}>
      <Renderer spec={spec} registry={registry} />
    </JSONUIProvider>
  );
}

Spec Structure (Flat Element Map)

规格结构(扁平元素映射)

The Ink schema uses a flat element map with a root key:
json
{
  "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": []
    }
  }
}
Ink 采用带根键的扁平元素映射结构:
json
{
  "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": []
    }
  }
}

Standard Components

标准组件

Layout

布局类

  • Box
    - Flexbox layout container (like a terminal
    <div>
    ). Use for grouping, spacing, borders, alignment. Default flexDirection is row.
  • Text
    - Text output with optional styling (color, bold, italic, etc.)
  • Newline
    - Inserts blank lines. Must be inside a Box with flexDirection column.
  • Spacer
    - Flexible empty space that expands along the main axis.
  • Box
    - Flexbox布局容器(类似终端中的
    <div>
    )。用于分组、间距设置、边框和对齐。默认flexDirection为row。
  • Text
    - 支持可选样式(颜色、加粗、斜体等)的文本输出
  • Newline
    - 插入空行。必须放在flexDirection为column的Box内。
  • Spacer
    - 可沿主轴扩展的灵活空白区域

Content

内容类

  • Heading
    - Section heading (h1: bold+underlined, h2: bold, h3: bold+dimmed, h4: dimmed)
  • Divider
    - Horizontal separator with optional centered title
  • Badge
    - Colored inline label (variants: default, info, success, warning, error)
  • Spinner
    - Animated loading spinner with optional label
  • ProgressBar
    - Horizontal progress bar (0-1)
  • Sparkline
    - Inline chart using Unicode block characters
  • BarChart
    - Horizontal bar chart with labels and values
  • Table
    - Tabular data with headers and rows
  • List
    - Bulleted or numbered list
  • ListItem
    - Structured list row with title, subtitle, leading/trailing text
  • Card
    - Bordered container with optional title
  • KeyValue
    - Key-value pair display
  • Link
    - Clickable URL with optional label
  • StatusLine
    - Status message with colored icon (info, success, warning, error)
  • Markdown
    - Renders markdown text with terminal styling
  • 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文本

Interactive

交互类

  • TextInput
    - Text input field (events: submit, change)
  • Select
    - Selection menu with arrow key navigation (events: change)
  • MultiSelect
    - Multi-selection with space to toggle (events: change, submit)
  • ConfirmInput
    - Yes/No confirmation prompt (events: confirm, deny)
  • Tabs
    - Tab bar navigation with left/right arrow keys (events: change)
  • TextInput
    - 文本输入框(事件:submit、change)
  • Select
    - 支持箭头键导航的选择菜单(事件:change)
  • MultiSelect
    - 支持空格键切换的多选菜单(事件:change、submit)
  • ConfirmInput
    - 是/否确认提示框(事件:confirm、deny)
  • Tabs
    - 支持左右箭头键导航的标签栏(事件:change)

Visibility Conditions

可见性条件

Use
visible
on elements to show/hide based on state. Syntax:
{ "$state": "/path" }
,
{ "$state": "/path", "eq": value }
,
{ "$state": "/path", "not": true }
,
{ "$and": [cond1, cond2] }
for AND,
{ "$or": [cond1, cond2] }
for OR.
可在元素上使用
visible
属性,根据状态显示/隐藏元素。语法:
{ "$state": "/path" }
{ "$state": "/path", "eq": value }
{ "$state": "/path", "not": true }
,使用
{ "$and": [cond1, cond2] }
表示逻辑与,
{ "$or": [cond1, cond2] }
表示逻辑或。

Dynamic Prop Expressions

动态属性表达式

Any prop value can be a data-driven expression resolved at render time:
  • { "$state": "/state/key" }
    - reads from state model (one-way read)
  • { "$bindState": "/path" }
    - two-way binding: use on the natural value prop of form components
  • { "$bindItem": "field" }
    - two-way binding to a repeat item field
  • { "$cond": <condition>, "$then": <value>, "$else": <value> }
    - conditional value
  • { "$template": "Hello, ${/name}!" }
    - interpolates state values into strings
Components do not use a
statePath
prop for two-way binding. Use
{ "$bindState": "/path" }
on the natural value prop instead.
任何属性值都可以是在渲染时解析的数据驱动表达式:
  • { "$state": "/state/key" }
    - 从状态模型读取数据(单向读取)
  • { "$bindState": "/path" }
    - 双向绑定:用于表单组件的天然值属性
  • { "$bindItem": "field" }
    - 双向绑定到重复项的字段
  • { "$cond": <condition>, "$then": <value>, "$else": <value> }
    - 条件值
  • { "$template": "Hello, ${/name}!" }
    - 将状态值插入字符串中
组件不使用
statePath
属性进行双向绑定,而是在天然值属性上使用
{ "$bindState": "/path" }

Event System

事件系统

Components use
emit
to fire named events. The element's
on
field maps events to action bindings:
tsx
CustomButton: ({ props, emit }) => (
  <Box>
    <Text>{props.label}</Text>
    {/* emit("press") triggers the action bound in the spec's on.press */}
  </Box>
),
json
{
  "type": "CustomButton",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit" } },
  "children": []
}
组件使用
emit
触发命名事件。元素的
on
字段将事件映射到动作绑定:
tsx
CustomButton: ({ props, emit }) => (
  <Box>
    <Text>{props.label}</Text>
    {/* emit("press") 会触发规格中on.press绑定的动作 */}
  </Box>
),
json
{
  "type": "CustomButton",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit" } },
  "children": []
}

Built-in Actions

内置动作

setState
,
pushState
, and
removeState
are built-in and handled automatically:
json
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
setState
pushState
removeState
是内置动作,会被自动处理:
json
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }

Repeat (Dynamic Lists)

重复渲染(动态列表)

Use the
repeat
field on a container element to render items from a state array:
json
{
  "type": "Box",
  "props": { "flexDirection": "column" },
  "repeat": { "statePath": "/items", "key": "id" },
  "children": ["item-row"]
}
Inside repeated children, use
{ "$item": "field" }
to read from the current item and
{ "$index": true }
for the current index.
可在容器元素上使用
repeat
字段,从状态数组中渲染项:
json
{
  "type": "Box",
  "props": { "flexDirection": "column" },
  "repeat": { "statePath": "/items", "key": "id" },
  "children": ["item-row"]
}
在重复的子元素中,使用
{ "$item": "field" }
读取当前项的数据,使用
{ "$index": true }
获取当前索引。

Streaming

流式渲染

Use
useUIStream
to progressively render specs from JSONL patch streams:
tsx
import { useUIStream } from "@json-render/ink";

const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });
使用
useUIStream
从JSONL补丁流逐步渲染规格:
tsx
import { useUIStream } from "@json-render/ink";

const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });

Server-Side Prompt Generation

服务端提示生成

Use the
./server
export to generate AI system prompts from your catalog:
typescript
import { catalog } from "./catalog";

const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });
使用
./server
导出,从你的组件目录生成AI系统提示词:
typescript
import { catalog } from "./catalog";

const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });

Providers

提供者(Providers)

ProviderPurpose
StateProvider
Share state across components (JSON Pointer paths). Accepts optional
store
prop for controlled mode.
ActionProvider
Handle actions dispatched via the event system
VisibilityProvider
Enable conditional rendering based on state
ValidationProvider
Form field validation
FocusProvider
Manage focus across interactive components
JSONUIProvider
Combined provider for all contexts
提供者用途
StateProvider
在组件间共享状态(使用JSON Pointer路径)。接受可选的
store
属性以启用受控模式。
ActionProvider
处理通过事件系统分发的动作
VisibilityProvider
启用基于状态的条件渲染
ValidationProvider
表单字段验证
FocusProvider
管理交互式组件间的焦点
JSONUIProvider
包含所有上下文的组合提供者

External Store (Controlled Mode)

外部存储(受控模式)

Pass a
StateStore
to
StateProvider
(or
JSONUIProvider
) to use external state management:
tsx
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
When
store
is provided,
initialState
and
onStateChange
are ignored.
StateStore
传递给
StateProvider
(或
JSONUIProvider
)以使用外部状态管理:
tsx
import { createStateStore, type StateStore } from "@json-render/ink";

const store = createStateStore({ count: 0 });

<StateProvider store={store}>{children}</StateProvider>

store.set("/count", 1); // React会自动重新渲染
当提供
store
时,
initialState
onStateChange
会被忽略。

createRenderer (Higher-Level API)

createRenderer(高级API)

tsx
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" }} />
);
tsx
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";

const InkRenderer = createRenderer(catalog, {
  ...standardComponents,
  // 在此处覆盖自定义组件
});

// InkRenderer包含所有提供者(状态、可见性、动作、焦点)
render(
  <InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);

Key Exports

核心导出

ExportPurpose
defineRegistry
Create a type-safe component registry from a catalog
Renderer
Render a spec using a registry
createRenderer
Higher-level: creates a component with built-in providers
JSONUIProvider
Combined provider for all contexts
schema
Ink flat element map schema (includes built-in state actions)
standardComponentDefinitions
Catalog definitions for all standard components
standardActionDefinitions
Catalog definitions for standard actions
standardComponents
Pre-built component implementations
useStateStore
Access state context
useStateValue
Get single value from state
useBoundProp
Two-way binding for
$bindState
/
$bindItem
expressions
useActions
Access actions context
useAction
Get a single action dispatch function
useOptionalValidation
Non-throwing variant of useValidation
useUIStream
Stream specs from an API endpoint
createStateStore
Create a framework-agnostic in-memory
StateStore
StateStore
Interface for plugging in external state management
Components
Typed component map (catalog-aware)
Actions
Typed action map (catalog-aware)
ComponentContext
Typed component context (catalog-aware)
flatToTree
Convert flat element map to tree structure
导出项用途
defineRegistry
从组件目录创建类型安全的组件注册表
Renderer
使用注册表渲染规格
createRenderer
高级API:创建包含内置提供者的组件
JSONUIProvider
包含所有上下文的组合提供者
schema
Ink扁平元素映射的规格(包含内置状态动作)
standardComponentDefinitions
所有标准组件的目录定义
standardActionDefinitions
标准动作的目录定义
standardComponents
预构建的组件实现
useStateStore
访问状态上下文
useStateValue
从状态中获取单个值
useBoundProp
$bindState
/
$bindItem
表达式提供双向绑定
useActions
访问动作上下文
useAction
获取单个动作的调度函数
useOptionalValidation
useValidation
的非抛出变体
useUIStream
从API端点流式获取规格
createStateStore
创建与框架无关的内存
StateStore
StateStore
用于接入外部状态管理的接口
Components
类型化的组件映射(感知目录)
Actions
类型化的动作映射(感知目录)
ComponentContext
类型化的组件上下文(感知目录)
flatToTree
将扁平元素映射转换为树结构

Terminal UI Design Guidelines

终端UI设计指南

  • Use Box for layout (flexDirection, padding, gap). Default flexDirection is row.
  • Terminal width is ~80-120 columns. Prefer vertical layouts (flexDirection: column) for main structure.
  • Use borderStyle on Box for visual grouping (single, double, round, bold).
  • Use named terminal colors: red, green, yellow, blue, magenta, cyan, white, gray.
  • Use Heading for section titles, Divider to separate sections, Badge for status, KeyValue for labeled data, Card for bordered groups.
  • Use Tabs for multi-view UIs with visible conditions on child content.
  • Use Sparkline for inline trends and BarChart for comparing values.
  • 使用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作为边框容器。
  • 使用Tabs实现多视图UI,结合子内容的可见性条件。
  • 使用Sparkline展示内联趋势,使用BarChart比较数值。