ink について
@json-render/ink は JSON 仕様をインタラクティブなターミナルコンポーネントツリーとして描画します。ターミナル UI の開発者向けで、標準コンポーネントに状態バインディング、イベント、アクション、仕様のストリーミングを組み合わせます。
主な機能
- 01JSON 仕様をターミナルに描画
- 02標準のレイアウト・コンテンツコンポーネント
- 03インタラクティブな入力とナビゲーション
- 04状態バインディングと条件付き表示
- 05JSONL 仕様パッチのストリーミング
活用シーン
- 01JSON 仕様からターミナル UI を構築
- 02状態を持つターミナルフォームやメニューの作成
- 03ストリーミング UI 仕様の段階的な描画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ json-render --skill inkインストール後、Claude Code で/inkを入力して読み込まれたか確認します。
詳細
@json-render/ink
JSON 仕様を、標準コンポーネント、データバインディング、表示条件、アクション、動的プロパティを備えたインタラクティブなターミナルコンポーネントツリーに変換する Ink ターミナルレンダラーです。
クイックスタート
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 条件には { "$and": [cond1, cond2] }、OR 条件には { "$or": [cond1, cond2] } を使用します。
動的なプロパティ式
任意のプロパティ値に、レンダリング時に解決されるデータ駆動型の式を指定できます。
{ "$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(動的リスト)
コンテナー要素の 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." });
プロバイダー
| プロバイダー | 用途 |
|---|---|
StateProvider | コンポーネント間で状態を共有します(JSON Pointer パス)。制御モード用のオプションの store prop を受け取ります。 |
ActionProvider | イベントシステムを介してディスパッチされたアクションを処理します |
VisibilityProvider | 状態に基づく条件付きレンダリングを有効にします |
ValidationProvider | フォームフィールドの検証を行います |
FocusProvider | インタラクティブなコンポーネント間のフォーカスを管理します |
JSONUIProvider | すべてのコンテキストをまとめたプロバイダーです |
外部ストア(制御モード)
StateProvider(または JSONUIProvider)に StateStore を渡すと、外部の状態管理を使用できます。
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 | カタログから型安全なコンポーネントレジストリを作成します |
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 | フラットな要素マップをツリー構造に変換します |
ターミナル 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 を使用します。
- インラインの傾向表示には Sparkline、値の比較には BarChart を使用します。