Skill v1.0.1
currentLLM-judged scan95/1001 files
version: "1.0.1" name: ink description: Ink terminal renderer for json-render that turns JSON specs into interactive terminal UIs. Use when working with @json-render/ink, building terminal UIs from JSON, creating terminal component catalogs, or rendering AI-generated specs in the terminal.
@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 componentsconst 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>,});// Renderfunction 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 titleBadge- Colored inline label (variants: default, info, success, warning, error)Spinner- Animated loading spinner with optional labelProgressBar- Horizontal progress bar (0-1)Sparkline- Inline chart using Unicode block charactersBarChart- Horizontal bar chart with labels and valuesTable- Tabular data with headers and rowsList- Bulleted or numbered listListItem- Structured list row with title, subtitle, leading/trailing textCard- Bordered container with optional titleKeyValue- Key-value pair displayLink- Clickable URL with optional labelStatusLine- 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.