12k

@json-render/ink

Terminal renderer for Ink with multiple standard components, providers, hooks, and streaming support.

Installation#

npm install @json-render/core @json-render/ink

Peer dependencies: react ^18.0.0 || ^19.0.0, ink ^6.0.0, and zod ^4.0.0.

npm install react ink zod

Standard Components#

Layout#

ComponentPropsDescription
BoxflexDirection, alignItems, justifyContent, gap, padding, margin, borderStyle, borderColor, width, height, display, overflowFlexbox layout container (like a terminal div)
Spacer(none)Flexible empty space that expands to fill available room
NewlinecountInsert blank lines

Content#

ComponentPropsDescription
Texttext, color, bold, italic, underline, strikethrough, dimColor, inverse, wrapText output with styling
Headingtext, level (h1-h4), colorSection heading
Dividercharacter, color, dimColor, title, widthHorizontal separator line with optional title
Badgelabel, variantColored inline label (default, info, success, warning, error)
Spinnerlabel, colorAnimated loading spinner
ProgressBarprogress (0-1), width, color, labelHorizontal progress bar
StatusLinetext, status, iconStatus message with colored icon
KeyValuelabel, value, labelColor, separatorKey-value pair display
Linkurl, label, colorRenders a URL as underlined text. Shows "label (url)" when label is provided.
MarkdowntextRenders markdown with terminal styling (headings, bold, italic, code, lists, blockquotes, horizontal rules)

Data#

ComponentPropsDescription
Tablecolumns, rows, borderStyle, headerColorTabular data with headers
Listitems, ordered, bulletChar, spacingBulleted or numbered list
ListItemtitle, subtitle, leading, trailingStructured list row
Cardtitle, borderStyle, borderColor, paddingBordered container with optional title
Sparklinedata, width, color, label, min, maxInline sparkline chart using Unicode blocks (▁▂▃▄▅▆▇█)
BarChartdata (label/value/color), width, showValues, showPercentageHorizontal bar chart for comparing values

Interactive#

ComponentPropsDescription
TextInputplaceholder, value (use $bindState), label, maskText input field. Press Enter to submit.
Selectoptions, value (use $bindState), labelArrow-key selection menu
MultiSelectoptions, value (use $bindState), label, min, maxMulti-selection menu. Space to toggle, Enter to confirm.
ConfirmInputmessage, defaultValue, yesLabel, noLabelYes/No confirmation prompt. Press Y or N.
Tabstabs, value (use $bindState), colorTab bar navigation with left/right arrow keys. Place child content inside with visible conditions.

Providers#

JSONUIProvider#

Convenience wrapper around all providers: StateProviderVisibilityProviderValidationProviderActionProviderFocusProvider.

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>
PropTypeDescription
storeStateStoreExternal store (controlled mode). When provided, initialState and onStateChange are ignored.
initialStateRecord<string, unknown>Initial state model (uncontrolled mode).
onStateChange(changes: Array<{ path: string; value: unknown }>) => voidCallback 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";
ExportPurpose
standardComponentDefinitionsCatalog definitions for all 19 standard components
standardActionDefinitionsCatalog definitions for standard actions (setState, pushState, removeState, log, exit)
schemaInk 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).