Ink

React for CLIs — ターミナル UI をコンポーネントで構築するフレームワーク

1 — 概要

Ink は React のコンポーネント体験をコマンドラインアプリにそのまま持ち込むフレームワーク。内部では Facebook 製の Yoga Flexbox エンジンを使ってターミナルのレイアウトを計算し、chalk / ANSI 経由でテキストスタイルを適用する。

React のすべての機能 — Suspense・Concurrent Rendering・useTransition ・Context・カスタムフック — がそのまま使える。

6Built-in Components
11Built-in Hooks
30+公式 Examples
npmink + react のみで使用可
Claude CodeAnthropic — AI コーディングツール
Gemini CLIGoogle — AI エージェント CLI
GitHub Copilot CLIGitHub
Cloudflare WranglerWorkers CLI
Shopify CLIアプリ・テーマ開発
Prismaデータレイヤー
GatsbyWeb フレームワーク
Terraform CDKHashiCorp
npm install ink react

# スターターテンプレート
npx create-ink-app my-app           # JavaScript
npx create-ink-app --typescript my-app  # TypeScript
// counter.jsx
import React, {useState, useEffect} from 'react';
import {render, Text} from 'ink';

const Counter = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setCount(c => c + 1), 100);
    return () => clearInterval(timer);
  }, []);
  return <Text color="green">{count} iterations</Text>;
};

render(<Counter />);
2 — アーキテクチャ

Ink は カスタム React レンダラー として実装されている。ブラウザの DOM の代わりに独自の仮想 DOM を持ち、Yoga でレイアウトを計算してから ANSI 文字列を生成する。

JSX コンポーネント React Reconciler Ink Virtual DOM Yoga Flexbox Layout ANSI 文字列生成 log-update → stdout

log-update が前フレームと差分計算し、変更行だけ書き換えることでちらつきを抑制する。

ソースファイル 責務
reconciler.tsreact-reconciler を使ったカスタムレンダラー。DOM ノードの生成・更新・削除を管理。
dom.tsInk 独自の仮想 DOM 型定義(DOMNode / DOMElement)。
styles.tsJSX props を Yoga スタイルにマッピング。Flexbox・padding・margin・border・color 等。
render-node-to-output.tsDOM ツリーを ANSI 文字列に変換。境界・背景・テキスト装飾を適用。
log-update.ts前フレームとの差分を計算して stdout に書き出す。ちらつき抑制。
ink.tsxアプリルート。イベントループ管理・stdin/stdout セットアップ・FPS 制御。
renderer.tsYoga レイアウト計算のトリガーと出力バッファ管理。
input-parser.tsstdin バイト列をキーイベントにパース。kitty keyboard protocol 対応。
3 — コンポーネント
重要: すべてのテキストは <Text> 内に書く必要がある。<Box> は Flexbox コンテナとして機能し、直接テキストを配置することはできない。
コンポーネント 役割 主要 Props
<Text> テキスト表示・スタイリング color backgroundColor bold italic underline strikethrough inverse dimColor wrap
<Box> Flexbox コンテナ
<div style="display:flex"> 相当
flexDirection justifyContent alignItems flexWrap gap padding margin width height borderStyle borderColor backgroundColor overflow
<Newline> 改行挿入
<Text> 内でのみ使用可
count (default: 1)
<Spacer> Flexbox の余白を埋める伸縮スペーサー なし
<Static> スクロールバッファに固定出力
完了ログ・通知など永続表示に使用
items (Array)  style
<Transform> 子の文字列出力を変換
ライン単位で加工できる
transform(outputLine, index)

borderStyle に指定できる組み込み値: single / double / round / bold / singleDouble / doubleSingle / classic またはカスタムオブジェクト(各辺の文字を個別指定)。

<Box borderStyle="round" borderColor="green" padding={1}>
  <Text>Round green border</Text>
</Box>

<Box
  borderStyle={{
    topLeft: '╔', top: '═', topRight: '╗',
    bottomLeft: '╚', bottom: '═', bottomRight: '╝',
    left: '║', right: '║'
  }}
>
  <Text>Custom border</Text>
</Box>
4 — フック
フック 役割 主な返り値 / 引数
useInput(handler, opts?) キーボード入力ハンドラ登録 handler(input: string, key: Key)key.leftArrow / key.ctrl / key.return 等。opts: isActive
usePaste(handler, opts?) ペースト(bracketed paste)処理 handler(text: string)useInput と別チャンネルで共存可
useApp() アプリのライフサイクル制御 exit(errorOrResult?)  waitUntilRenderFlush()
useStdin() stdin ストリームへのアクセス stdin  isRawModeSupported  setRawMode(bool)
useStdout() stdout ストリームへのアクセス stdout  write(data)
useStderr() stderr ストリームへのアクセス stderr  write(data)
useWindowSize() ターミナルサイズ取得(リサイズ時に再レンダー) columns: number  rows: number
useFocus(opts?) フォーカス状態の取得(Tab で移動) isFocused: boolean。opts: autoFocus  isActive  id
useFocusManager() フォーカス管理の制御 enableFocus()  disableFocus()  focusNext()  focusPrevious()  focus(id)  activeId
useCursor() カーソル位置制御(IME サポート向け) setCursorPosition({x, y} | undefined)
useBoxMetrics(ref) Box 要素のレイアウト寸法取得 width  height  left  top  hasMeasured
import {useInput, useApp} from 'ink';

const MyApp = () => {
  const {exit} = useApp();
  useInput((input, key) => {
    if (input === 'q') exit();
    if (key.leftArrow) { /* 左移動 */ }
    if (key.ctrl && input === 'c') exit();
  });
  return <Text>Press q to quit</Text>;
};
5 — API

コンポーネントをマウントしてターミナルに出力する。Instance オブジェクトを返す。

import {render} from 'ink';

const {rerender, unmount, waitUntilExit} = render(<MyApp />);

rerender(<MyApp count={1} />);   // props 更新
await waitUntilExit();             // 終了まで待機
オプション 型 / デフォルト 説明
stdoutWritable / process.stdout出力先ストリーム
stdinReadable / process.stdin入力ストリーム
exitOnCtrlCboolean / trueCtrl+C でアプリを終了するか
patchConsoleboolean / trueconsole.log を Ink 出力と混ざらないようにパッチ
maxFpsnumber / 30最大フレームレート(fps)
incrementalRenderingboolean / false変更行のみ再描画してちらつきを軽減
concurrentboolean / falseReact Concurrent モード(Suspense・useTransition が有効)
alternateScreenboolean / falseターミナルの alternate screen バッファで起動(終了時に元の表示を復元)
interactiveboolean / autoインタラクティブモード。非 TTY / CI では自動で false
kittyKeyboardobject / undefinedkitty keyboard protocol。修飾キー詳細・press/repeat/release イベントを取得
isScreenReaderEnabledboolean / envスクリーンリーダーモード(INK_SCREEN_READER=true でも有効)
debugboolean / false各更新を上書きせず新規出力として表示
onRender({renderTime}) => void各レンダー完了後のコールバック
メソッド説明
rerender(tree)ルートノードを新しいツリーで置き換え、または現在の props を更新
unmount()アプリを手動でアンマウント
waitUntilExit()アンマウントまで待機する Promise。exit(value) で resolve、exit(error) で reject
waitUntilRenderFlush()保留中のレンダー出力が stdout にフラッシュされるまで待機
cleanup()アンマウントして内部インスタンスを解放(テスト向け)
clear()ターミナル出力をクリア

コンポーネントを文字列に同期レンダーする。stdout に書き出さないため、ドキュメント生成・テスト・ファイル出力に使用。columns(デフォルト: 80)でテキスト折り返し幅を指定。

import {renderToString, Text, Box} from 'ink';

const output = renderToString(
  <Box padding={1}>
    <Text color="green">Hello World</Text>
  </Box>,
  {columns: 40}
);
// → '\n Hello World \n'
6 — テスト・CI・その他

ink-testing-library でコンポーネントをヘッドレスレンダーし、lastFrame() で出力文字列を検証できる。

import React from 'react';
import {Text} from 'ink';
import {render} from 'ink-testing-library';

const Hello = ({name}) => <Text>Hello, {name}!</Text>;

const {lastFrame} = render(<Hello name="World" />);
lastFrame() === 'Hello, World!'; // => true
機能詳細
CI モード 環境変数 CI が設定されると、終了時に最後のフレームのみ出力(ANSI 上書きシーケンスを使用しない)。CI=false で無効化可。
React Devtools オプション依存として react-devtools-core をインストール後、DEV=true my-cli で起動し npx react-devtools で接続。コンポーネントツリーのインスペクト・props のライブ変更が可能。
スクリーンリーダー <Box aria-role="…" aria-label="…" aria-hidden> で ARIA 情報を付与。render(…, {isScreenReaderEnabled: true}) または環境変数 INK_SCREEN_READER=true で有効化。
kitty keyboard protocol mode: 'auto' で対応ターミナルを自動検出。Super / Hyper / CapsLock・press / repeat / release イベントが使用可能。
Incremental Rendering incrementalRendering: true で変更された行のみ再描画。高頻度更新 UI のパフォーマンスを大幅に改善。
Alternate Screen alternateScreen: true でターミナルの alt バッファを使用(vim / htop 方式)。終了時に以前の表示を復元。
アプリのライフサイクル: Ink アプリは Node.js プロセスとして動作し、イベントループに作業がある間だけ生存する。非同期処理がなければ 1 フレームレンダーして即終了する。useInput のリスニング・タイマー・Pending Promise などがプロセスを継続させる。useApp().exit() を呼ぶと waitUntilExit() が解決して終了する。