
Ink
- 891 installs
- 15.8k repo stars
- Updated July 8, 2026
- vercel-labs/json-render
ink is an agent skill for @json-render/ink that renders JSON specs into interactive terminal UIs using Ink component catalogs and registries.
About
The ink skill documents @json-render/ink, an Ink terminal renderer that converts JSON specs into interactive terminal component trees. It uses defineCatalog with the Ink schema, standardComponentDefinitions, and standardActionDefinitions from the catalog package. Custom components extend the catalog via defineRegistry while standard components are built-in. Specs use a flat element map with a root key pointing to the top-level element, supporting data binding, visibility conditions, actions, and dynamic props. Quick start covers JSONUIProvider wrapping a Renderer with spec and registry props. The skill explains component catalog structure, action definitions, and rendering patterns for AI-generated terminal UIs. Use when building terminal UIs from JSON, working with @json-render/ink, or creating terminal component catalogs.
- Converts JSON specs to Ink terminal component trees.
- Flat element map spec structure with root key.
- defineCatalog plus defineRegistry for custom components.
- Built-in standard components and action definitions.
- JSONUIProvider wraps Renderer with spec and registry.
Ink by the numbers
- 891 all-time installs (skills.sh)
- +24 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #410 of 2,277 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
ink capabilities & compatibility
- Capabilities
- json spec to ink rendering · component catalog definition · custom registry registration · flat element map structure · action and visibility binding
- Use cases
- frontend · ui design
npx skills add https://github.com/vercel-labs/json-render --skill inkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 891 |
|---|---|
| repo stars | ★ 15.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 8, 2026 |
| Repository | vercel-labs/json-render ↗ |
How do I render AI-generated JSON specs as interactive terminal UIs with Ink?
Render JSON specs into interactive terminal UIs using @json-render/ink with catalogs, registries, and flat element maps.
Who is it for?
Developers building terminal UIs from JSON specs with @json-render/ink and custom component catalogs.
Skip if: Skip when you need web React rendering instead of terminal Ink output.
When should I use this skill?
User works with @json-render/ink, builds terminal UIs from JSON, or creates terminal component catalogs.
What you get
A working Ink terminal UI rendered from a JSON spec with catalog-defined components and actions.
- Ink component catalog
- Renderer and provider wiring
- Runnable terminal UI from JSON spec
By the numbers
- Documents 19 standard Ink components and 5 built-in actions (setState, pushState, removeState, log, exit)
Files
@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 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>
);
}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 automaticallyWhen 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.
Related skills
How it compares
Pick ink over hand-written Ink layouts when you already standardize UI as json-render specs shared across web and terminal renderers.
FAQ
What does the ink skill cover?
@json-render/ink setup with defineCatalog, defineRegistry, flat element map specs, and Renderer usage.
When should I use it?
When rendering JSON specs as interactive terminal UIs or extending the Ink component catalog.
Is ink safe to install?
Review the Security Audits panel on this page before installing in production.