I

ink

by @vercel-labsv
4.3(20)

将 JSON 规范转换为交互式终端组件的渲染器,用于构建命令行界面

inkapibackendserverautomationGitHub
安装方式
npx skills add https://github.com/vercel-labs/json-render --skill ink
compare_arrows

Before / After 效果对比

1 组
使用前

在不使用 `ink` 的情况下,构建一个包含标题、文本、布局框等元素的结构化命令行界面(CLI)通常需要开发者手动处理大量的终端输出逻辑。这包括直接使用 `console.log` 进行文本输出,通过拼接字符串和计算填充来实现布局对齐,以及手动插入 ANSI 转义码来控制文本颜色、加粗、下划线等样式。对于复杂的布局,开发者需要编写冗长的代码来管理组件层级和动态更新,每个 UI 元素(如分隔线、加载动画)都需要从头实现或通过低级原语组合,耗时且易错,导致开发效率低下,且难以维护。

使用后

借助 `ink`,开发者可以通过简洁的 JSON 规范来声明式地定义整个终端用户界面。只需在 JSON 中描述 `Box`、`Heading`、`Text`、`Divider`、`Spinner` 等标准组件及其属性和层级关系,`ink` 的渲染器就能自动将其转换为交互式的终端组件树。这极大地简化了 CLI 界面的构建过程,将布局、样式和组件间关系的管理工作交由框架处理,开发者无需再手动操作复杂的 ANSI 码或编写繁琐的渲染逻辑,从而能够快速构建出结构清晰、功能丰富的命令行应用界面,显著提升开发效率和可维护性。

SKILL.md

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.

Quick Start

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

Spec Structure (Flat Element Map)

The Ink schema uses a flat element map with a root key:

{
  "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.

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

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)

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.

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.

Event System

Components use emit to fire named events. The element's on field maps events to action bindings:

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": []
}

Built-in Actions

setState, pushState, and removeState are built-in and handled automatically:

{ "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:

{
  "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.

Streaming

Use useUIStream to progressively render specs from JSONL patch streams:

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:

import { catalog } from "./catalog";

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

Providers

Provider Purpose

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

External Store (Controlled Mode)

Pass a StateStore to StateProvider (or JSONUIProvider) to use external state management:

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.

createRenderer (Higher-Level 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" }} />
);

Key Exports

Export Purpose

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

Terminal UI Design Guidelines

  • 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.

Weekly Installs260Repositoryvercel-labs/json-renderGitHub Stars13.3KFirst Seen2 days agoSecurity AuditsGen Agent Trust HubPassSocketPassSnykPassInstalled oncodex256gemini-cli254cursor254opencode254kimi-cli253github-copilot253

用户评价 (0)

发表评价

效果
易用性
文档
兼容性

暂无评价

统计数据

安装量1.2K
评分4.3 / 5.0
版本
更新日期2026年9月19日
对比案例1 组

用户评分

4.3(20)
5
40%
4
50%
3
10%
2
0%
1
0%

为此 Skill 评分

0.0

兼容平台

🔧Claude Code

时间线

创建2026年3月26日
最后更新2026年9月19日
🎁 Agent 知识卡片
调研问卷