Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

ink — Claude Code skill: what it does, how to install

Builds interactive terminal user interfaces from JSON specs using @json-render/ink. Useful when creating dynamic TUI components or agent-generated layouts.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add vercel-labs/json-render --skill ink -a claude-code
Open on GitHub

What ink does

This skill guides the assistant in using @json-render/ink to build terminal UIs from JSON schemas. It handles flat element maps, turning them into layouts with Box, Text, Table, BarChart, and interactive controls like Select, Tabs, or ConfirmInput.

Dynamic prop expressions resolve values at render time using $state, $cond, and string templates. Built-in actions handle setState, pushState, and removeState out of the box. For real-time updates, useUIStream renders incoming JSONL patch streams directly into the terminal tree.

Input components do not accept a standalone statePath property for two-way binding. You must pass { "$bindState": "/path" } directly into the component's value prop instead.

What it helps with

  • generate terminal user interfaces from flat JSON element maps
  • configure two-way form bindings with $bindState
  • add interactive terminal menus and tabs with arrow key navigation
  • create model system prompts using the catalog prompt helper

Example prompts for the agent

  • Generate a JSON spec for an Ink terminal dashboard with a heading and data table
  • Create an interactive terminal form using @json-render/ink with TextInput and ConfirmInput
  • Set up an Ink catalog with custom components and two-way state binding

How to install ink

1Skills CLI (Vercel)

npx skills add vercel-labs/json-render --skill ink -a claude-code

2Manually: put the files in place

~/.claude/skills/ink/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/ink/

From the same set20

The whole set →
react vercel-labs This skill turns json-render JSON specs into React component trees. Use it when building JSON-driven UIs or defining a component catalog for generated specs. Claude Code CX Codex Cursor +1 16,300installs core vercel-labs Defines the schema and component catalog for json-render and builds the system prompt that makes an AI emit JSON UI specs. Claude Code CX Codex Cursor +1 15,000installs codegen vercel-labs Utilities for generating code from json-render UI trees. Build custom code exporters for Next.js, Remix, and other frameworks, traverse specs, and serialize props. Claude Code CX Codex Cursor +1 8,900installs next vercel-labs Converts JSON specs into Next.js apps with routes, layouts, SSR, and metadata. Handy when building AI-generated multi-page apps with @json-render/next. Claude Code CX Codex Cursor +1 8,600installs devtools vercel-labs Debug panel for json-render apps: component tree, state editor, actions log, streaming patches, and element picker. Claude Code CX Codex Cursor +1 8,300installs directives vercel-labs Pre-built directives for json-render: formatting, math, string handling, i18n. Wire them into your catalog and renderer with one import. Claude Code CX Codex Cursor +1 8,000installs mcp vercel-labs This skill helps build MCP servers that render interactive json-render UIs in Claude, ChatGPT, Cursor, and VS Code. Claude Code CX Codex Cursor +1 5,800installs react-native vercel-labs This skill turns JSON specs into native React Native mobile UIs. Use it when screens need state bindings, conditional visibility, and actions. Claude Code CX Codex Cursor +1 3,200installs

Similar

kill-ai-slop yetone Finds and removes the generic visual and copy tics that make a site look machine-generated. Use it when a landing page or UI feels templated and needs a human touch. Claude Code CX Codex Cursor +1 3,200installs render-textures phaserjs Explains off-screen rendering in Phaser 4, covering RenderTexture game objects, DynamicTexture assets, Stamp helpers, and command buffers. Claude Code CX Codex Cursor +1 767installs physics-matter phaserjs Helps AI agents configure Matter.js physics in Phaser 4, including rigid bodies, composite shapes, constraints, and mouse dragging. Claude Code CX Codex Cursor +1 754installs frontend-ui-dark-ts sickn33 Build dark-themed React interfaces with Tailwind CSS and Framer Motion. It guides dashboard, admin panel, and data-dense UI styling. Claude Code CX Codex Cursor +1 1,100installs sign-in-with-google-web official google Guides Google Identity Services setup across web projects. It implements secure token handling and IETF authentication patterns. Claude Code Gemini CLI CX Codex 743installs v3-to-v4-migration phaserjs Guides migration from Phaser 3 to Phaser 4. Explains renderer architecture changes, unified filters, and tint system updates. Claude Code CX Codex Cursor +1 711installs frontend-design official anthropics This skill guides intentional visual design for new interfaces and redesigns. Use it when a generic generated UI is not enough. Claude Code Claude.ai 1,025,790installs design-taste-frontend Leonxlnx This skill directs the design of public pages and work screens, then checks the result in a browser through screenshots. Use it when an interface feels generic or unfinished. Claude Code CX Codex Cursor +1 673,100installs

Questions about ink

How to install ink in Claude Code?

Run npx skills add vercel-labs/json-render --skill ink -a claude-code in your terminal.

What does the ink skill do?

It instructs the coding assistant how to render interactive terminal UIs from JSON specs using the @json-render/ink component tree.

How is two-way data binding configured for components?

Pass { "$bindState": "/path" } to the component's standard value property. Components do not use a separate statePath property.

Source text

SKILL.md · Apache-2.0
---
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

```typescript
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:

```json
{
  "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 title
- `Badge` - Colored inline label (variants: default, info, success, warning, error)
- `Spinner` - Animated loading spinner with optional label
- `ProgressBar` - Horizontal progress bar (0-1)
- `Sparkline` - Inline chart using Unicode block characters
- `BarChart` - Horizontal bar chart with labels and values
- `Table` - Tabular data with headers and rows
- `List` - Bulleted or numbered list
- `ListItem` - Structured list row with title, subtitle, leading/trailing text
- `Card` - Bordered container with optional title
- `KeyValue` - Key-value pair display
- `Link` - Clickable URL with optional label
- `StatusLine` - 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:

```tsx
CustomButton: ({ props, emit }) => (
  <Box>
    <Text>{props.label}</Text>
    {/* emit("press") triggers the action bound in the spec's on.press */}
  </Box>
),
```

```json
{
  "type": "CustomButton",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit" } },
  "children": []
}
```

## Built-in Actions

`setState`, `pushState`, and `removeState` are built-in and handled automatically:

```json
{ "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:

```json
{
  "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.

For nested lists, an inner repeat can use `{ "statePath": { "$item": "children" } }` to iterate an array on the enclosing item.

## Streaming

Use `useUIStream` to progressively render specs from JSONL patch streams:

```tsx
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:

```typescript
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:

```tsx
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)

```tsx
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.

© vercel-labs · Apache-2.0 · vercel-labs/json-render

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor