@json-render/ink
Terminal renderer for Ink with multiple standard components, providers, hooks, and streaming support.
Installation#
npm install @json-render/core @json-render/inkPeer dependencies: react ^18.0.0 || ^19.0.0, ink ^6.0.0, and zod ^4.0.0.
npm install react ink zodStandard Components#
Layout#
| Component | Props | Description |
|---|---|---|
Box | flexDirection, alignItems, justifyContent, gap, padding, margin, borderStyle, borderColor, width, height, display, overflow | Flexbox layout container (like a terminal div) |
Spacer | (none) | Flexible empty space that expands to fill available room |
Newline | count | Insert blank lines |
Content#
| Component | Props | Description |
|---|---|---|
Text | text, color, bold, italic, underline, strikethrough, dimColor, inverse, wrap | Text output with styling |
Heading | text, level (h1-h4), color | Section heading |
Divider | character, color, dimColor, title, width | Horizontal separator line with optional title |
Badge | label, variant | Colored inline label (default, info, success, warning, error) |
Spinner | label, color | Animated loading spinner |
ProgressBar | progress (0-1), width, color, label | Horizontal progress bar |
StatusLine | text, status, icon | Status message with colored icon |
KeyValue | label, value, labelColor, separator | Key-value pair display |
Link | url, label, color | Renders a URL as underlined text. Shows "label (url)" when label is provided. |
Markdown | text | Renders markdown with terminal styling (headings, bold, italic, code, lists, blockquotes, horizontal rules) |
Data#
| Component | Props | Description |
|---|---|---|
Table | columns, rows, borderStyle, headerColor | Tabular data with headers |
List | items, ordered, bulletChar, spacing | Bulleted or numbered list |
ListItem | title, subtitle, leading, trailing | Structured list row |
Card | title, borderStyle, borderColor, padding | Bordered container with optional title |
Sparkline | data, width, color, label, min, max | Inline sparkline chart using Unicode blocks (▁▂▃▄▅▆▇█) |
BarChart | data (label/value/color), width, showValues, showPercentage | Horizontal bar chart for comparing values |
Interactive#
| Component | Props | Description |
|---|---|---|
TextInput | placeholder, value (use $bindState), label, mask | Text input field. Press Enter to submit. |
Select | options, value (use $bindState), label | Arrow-key selection menu |
MultiSelect | options, value (use $bindState), label, min, max | Multi-selection menu. Space to toggle, Enter to confirm. |
ConfirmInput | message, defaultValue, yesLabel, noLabel | Yes/No confirmation prompt. Press Y or N. |
Tabs | tabs, value (use $bindState), color | Tab bar navigation with left/right arrow keys. Place child content inside with visible conditions. |
Providers#
JSONUIProvider#
Convenience wrapper around all providers: StateProvider → VisibilityProvider → ValidationProvider → ActionProvider → FocusProvider.
import { JSONUIProvider, Renderer } from "@json-render/ink";
<JSONUIProvider initialState={{}} handlers={handlers}>
<Renderer spec={spec} registry={registry} />
</JSONUIProvider>StateProvider#
<StateProvider initialState={object} onStateChange={fn}>
{children}
</StateProvider>| Prop | Type | Description |
|---|---|---|
store | StateStore | External store (controlled mode). When provided, initialState and onStateChange are ignored. |
initialState | Record<string, unknown> | Initial state model (uncontrolled mode). |
onStateChange | (changes: Array<{ path: string; value: unknown }>) => void | Callback when state changes (uncontrolled mode). |
External Store (Controlled Mode)
Pass a StateStore to bypass internal state and wire json-render to any state management:
import { createStateStore } from "@json-render/ink";
const store = createStateStore({ count: 0 });
<StateProvider store={store}>
{children}
</StateProvider>
// Mutate from anywhere — components re-render automatically:
store.set("/count", 1);The store prop is also available on JSONUIProvider and createRenderer.
ActionProvider#
<ActionProvider handlers={Record<string, ActionHandler>} navigate={fn}>
{children}
</ActionProvider>Built-in actions: setState, pushState, removeState, log, exit. Custom handlers override built-ins. Includes a terminal confirmation dialog (press Y/N) for actions with confirm.
VisibilityProvider#
<VisibilityProvider>
{children}
</VisibilityProvider>ValidationProvider#
<ValidationProvider>
{children}
</ValidationProvider>FocusProvider#
<FocusProvider>
{children}
</FocusProvider>Manages Tab-cycling focus between interactive components (TextInput, Select). Supports useFocusDisable to suppress cycling during modal dialogs.
defineRegistry#
Create a type-safe component registry. Standard components are built-in; only register custom components.
import { defineRegistry, type Components } from "@json-render/ink";
const { registry, handlers, executeAction } = defineRegistry(catalog, {
components: {
MyWidget: ({ props }) => <Text>{props.label}</Text>,
} as Components<typeof catalog>,
actions: {
submit: async (params, setState, state) => {
// custom action logic
},
},
});handlers is designed for JSONUIProvider/ActionProvider. executeAction is an imperative helper.
createRenderer#
Higher-level helper that wraps Renderer + all providers into a single component.
import { createRenderer } from "@json-render/ink";
const UIRenderer = createRenderer(catalog, components);
<UIRenderer spec={spec} state={initialState} />;Hooks#
useUIStream#
const {
spec, // Spec | null - current UI state
isStreaming, // boolean - true while streaming
error, // Error | null
send, // (prompt: string, context?: Record<string, unknown>) => Promise<void>
stop, // () => void - abort the current stream
clear, // () => void - reset spec and error
} = useUIStream({
api: string,
onComplete?: (spec: Spec) => void,
onError?: (error: Error) => void,
fetch?: (url: string, init?: RequestInit) => Promise<Response>,
validate?: boolean,
maxRetries?: number,
});useStateStore#
const { state, get, set, update } = useStateStore();useStateValue#
const value = useStateValue(path: string);useBoundProp#
const [value, setValue] = useBoundProp(resolvedValue, bindingPath);useActions#
const { execute } = useActions();useIsVisible#
const isVisible = useIsVisible(condition?: VisibilityCondition);useFocus#
const { isActive, id } = useFocus();useFocusDisable#
useFocusDisable(disabled: boolean);Suppresses Tab-cycling while disabled is true (e.g., during a modal dialog).
Catalog Exports#
import { standardComponentDefinitions, standardActionDefinitions } from "@json-render/ink/catalog";
import { schema } from "@json-render/ink/schema";| Export | Purpose |
|---|---|
standardComponentDefinitions | Catalog definitions for all 19 standard components |
standardActionDefinitions | Catalog definitions for standard actions (setState, pushState, removeState, log, exit) |
schema | Ink element tree schema |
Server Export#
import { schema, standardComponentDefinitions, standardActionDefinitions } from "@json-render/ink/server";Re-exports the schema and catalog definitions for server-side usage (e.g., building system prompts).