Claude
Skills
Sign in
Back

json-render-catalog

Included with Lifetime
$97 forever

json-render component catalog patterns for AI-safe generative UI. Define Zod-typed catalogs that constrain what AI can generate, use @json-render/shadcn for 36 pre-built components, optimize specs with YAML mode, and apply the three edit modes (patch/merge/diff) for progressive updates. Use when building AI-generated UIs, defining component catalogs, or integrating json-render into React/Vue/Svelte/React Native/Ink/Next.js projects.

Designjson-rendergenuizodcatalogshadcnai-uicomponent-catalogvercelscripts

What this skill does


# json-render Component Catalogs

json-render (Vercel Labs, 12.9K stars, Apache-2.0) is a framework for AI-safe generative UI. AI generates flat-tree JSON (or YAML) specs constrained to a developer-defined catalog — the catalog is the contract between your design system and AI output. If a component or prop is not in the catalog, AI cannot generate it.

## Storybook → catalog import (#1529, 2026-04)

When the project ships a Storybook setup, **import the catalog from Storybook stories** instead of hand-writing one. The bundled importer at `scripts/storybook-to-catalog.mjs` reads a `@storybook/addon-mcp` `list-all-documentation` manifest and emits a Zod-typed `catalog.ts` plus a `components.tsx` registry.

```bash
node "${CLAUDE_SKILL_DIR}/scripts/storybook-to-catalog.mjs" storybook-manifest.json \
  --out src/genui/catalog.ts \
  --components src/genui/components.tsx \
  --project-root .
```

Storybook becomes the single source of truth — adding a story automatically expands the AI-allowed surface; removing one shrinks it. AI safety is enforced at import: callbacks, raw object props, and `z.any()` are dropped. Full mapping: `references/storybook-import.md`. Companion fixture for testing: `references/storybook-fixture.json`.

## New in 2026-04 → 2026-05 (json-render 0.14 → 0.19)

- **Custom directives API (0.19)** — `@json-render/core` now ships `defineDirective`, letting you declare new JSON shapes (e.g. `$format`, `$math`) that resolve to computed values at render time. Directives compose by nesting and resolve inside-out. All four renderers (React, Vue, Svelte, Solid) have built-in directive resolution. This is the safe escape hatch for computed values without widening the catalog to `z.any()`.
- **`@json-render/directives` package (0.19)** — seven ready-made directives: `$format` (date / currency / number / percent via `Intl`), `$math` (add, subtract, multiply, divide, mod, min, max, round, floor, ceil, abs), `$concat`, `$count`, `$truncate`, `$pluralize`, `$join`. Plus `createI18nDirective` for `$t` translation keys with `{{param}}` interpolation, and `standardDirectives` for one-line registration. Register once, use in any spec — AI no longer needs string-mangling or duplicated literals.
- **Devtools ecosystem (0.18)** — five new packages: `@json-render/devtools` core + framework adapters for React, Vue, Svelte, Solid. Inspector panel has six tabs (Spec, State, Actions, Stream, Catalog, Pick) with DOM element picking that maps back to spec keys. Tree-shakes to `null` in production. Companion Next.js demo app shipped with AI-chat + catalog integration. Action observer infrastructure exposed for adapters to mirror events into the panel.
- **Zod 4 fix (0.18)** — `formatZodType` now correctly handles `z.record()`, `z.default()`, and `z.literal()` (previously produced empty/wrong prompt output).
- **Three edit modes (0.14)** — `patch` (RFC 6902), `merge` (RFC 7396), `diff` (unified) for progressive AI refinements. `buildEditUserPrompt()` + `diffToPatches()` + `deepMergeSpec()` in `@json-render/core`.
- **`@json-render/yaml` (0.14)** — official YAML wire format + streaming parser; `buildUserPrompt({ format: 'yaml' })`.
- **`@json-render/ink` (0.15)** — render catalogs to terminal UIs (Ink-based, 20+ components) using the same spec.
- **`@json-render/next` (0.16)** — generate full Next.js apps (routes, layouts, SSR, metadata) from a single spec.
- **`@json-render/shadcn-svelte` (0.16)** — 36-component Svelte 5 + Tailwind mirror of the React shadcn catalog.
- **shadcn catalog at 36 components** (was documented as 29 — the count was wrong even at 0.13). Use `@json-render/shadcn` as-is or `mergeCatalogs()` with your custom types.
- **`@json-render/react-three-fiber`** now ships 20 components including `GaussianSplat` (0.17).
- **`@json-render/mcp`** — upgrade plain MCP tool JSON into interactive iframes inside Claude/Cursor/ChatGPT conversations. See the `ork:mcp-visual-output` skill.
- **MCP multi-surface**: same spec renders to React, PDF (`@json-render/react-pdf`), email (`@json-render/react-email`), terminal (Ink), Next.js apps, and Remotion videos.

## Directives — @json-render/directives (0.19)

Directives are the safe escape hatch for computed values. AI emits a `$`-prefixed object, the renderer resolves it inside-out before the component receives props — the catalog stays strict (no `z.any()` widening) and the spec stays declarative. The `@json-render/directives` package ships seven prebuilt directives plus an i18n factory; `standardDirectives` exports them as one array for one-line registration. Directives nest freely (e.g. `$format` wrapping `$math`) and are resolved by all four renderer integrations (React, Vue, Svelte, Solid).

### Registration

```tsx
import { defineRegistry, JSONUIProvider, Renderer } from '@json-render/react'
import { standardDirectives, createI18nDirective } from '@json-render/directives'

const directives = [
  ...standardDirectives,
  createI18nDirective({
    locale: 'en',
    fallbackLocale: 'en',
    messages: { en: { greeting: 'Hello, {{name}}!' } },
  }),
]

const { registry } = defineRegistry(catalog, { components })

// directives register on the provider (RendererProps has no directives prop)
<JSONUIProvider registry={registry} directives={directives}>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>
```

### The seven prebuilt directives

| Directive | Purpose | Minimal usage |
|-----------|---------|---------------|
| `$format` | `Intl`-based formatting for `date`, `currency`, `number`, `percent`. Supports `locale`, `currency`, `notation`, and `style: "relative"` for human-readable date deltas. | `{ "$format": "currency", "value": 1299, "currency": "USD" }` → `$1,299.00` |
| `$math` | Arithmetic — `add`, `subtract`, `multiply`, `divide`, `mod`, `min`, `max`, `round`, `floor`, `ceil`, `abs`. Division by zero returns `0`; non-numeric inputs coerce to `0`. | `{ "$math": "multiply", "a": { "$state": "/qty" }, "b": 9.99 }` |
| `$concat` | Joins an array of dynamic values into a string, resolving each element through the directive pipeline first. | `{ "$concat": ["Hello, ", { "$state": "/user/name" }, "!"] }` |
| `$count` | Length of an array or string; `0` for anything else. | `{ "$count": { "$state": "/items" } }` |
| `$truncate` | Truncate to `length` (default 100) with optional `suffix` (default `...`). No-op if already short enough. | `{ "$truncate": { "$state": "/bio" }, "length": 80 }` |
| `$pluralize` | Singular/plural/zero selection. Prepends the count automatically (`"3 items"`, `"1 item"`, or the literal `zero` form). | `{ "$pluralize": { "$state": "/cart/count" }, "zero": "no items", "one": "item", "other": "items" }` |
| `$join` | Join an array with a `separator` (default `", "`). | `{ "$join": { "$state": "/tags" }, "separator": " · " }` |

`createI18nDirective({ locale, messages, fallbackLocale? })` registers a `$t` directive with `{{param}}` interpolation: `{ "$t": "greeting", "params": { "name": "Ada" } }`.

### Custom directives via `defineDirective`

`defineDirective` lives in `@json-render/core` (0.19+). A directive declares a Zod schema for its JSON shape and a `resolve(raw, ctx)` function — use `resolvePropValue(raw.field, ctx)` to recursively resolve any nested directive or state reference before computing.

```ts
import { defineDirective, resolvePropValue } from '@json-render/core'
import { z } from 'zod'

export const initialsDirective = defineDirective({
  name: '$initials',
  description: 'First letter of each word, uppercased.',
  schema: z.object({ $initials: z.unknown() }),
  resolve(raw, ctx) {
    const text = String(resolvePropValue(raw.$initials, ctx) ?? '')
    return text.split(/\s+/).map((w) => w[0]?.toUpperCase() ?? '').join('')
  },
})
```

Spread into the renderer alongside `standardDirectives`: `directives={[...standardDirectives, initialsDirective]}`. Keep the schema tight — directives are the only place where AI gets to emit non-catalog JSON, so let Zod en

Related in Design