Skip to content
Development
Skill

/react

React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.

From plugin
json-render
16k27 skills
Install
$ npx -y skills add vercel-labs/json-render --skill react --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/react

Context preview

The summary Claude sees to decide when to auto-load this skill.

React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.

SKILL.md

react.SKILL.md
name: react
description: React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.

@json-render/react

React renderer that converts JSON specs into React component trees.

Quick Start

import { defineRegistry, Renderer } from "@json-render/react";
import { catalog } from "./catalog";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) => <div>{props.title}{children}</div>,
  },
});

function App({ spec }) {
  return <Renderer spec={spec} registry={registry} />;
}

Creating a Catalog

import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry } from "@json-render/react";
import { z } from "zod";

// Create catalog with props schemas
export const catalog = defineCatalog(schema, {
  components: {
    Button: {
      props: z.object({
        label: z.string(),
        variant: z.enum(["primary", "secondary"]).nullable(),
      }),
      description: "Clickable button",
    },
    Card: {
      props: z.object({ title: z.string() }),
      slots: ["default"],
      description: "Card container with title",
    },
    Layout: {
      props: z.object({}),
      slots: ["default", "header", "footer"],
      description: "Layout with named content regions",
    },
  },
});

// Define component implementations with type-safe props
const { registry } = defineRegistry(catalog, {
  components: {
    Button: ({ props }) => (
      <button className={props.variant}>{props.label}</button>
    ),
    Card: ({ props, children }) => (
      <div className="card">
        <h2>{props.title}</h2>
        {children}
      </div>
    ),
    Layout: ({ children, slots }) => (
      <div>
        <header>{slots?.header}</header>
        <main>{children}</main>
        <footer>{slots?.footer}</footer>
      </div>
    ),
  },
});

Spec Structure (Element Tree)

The React schema uses an element tree format:

{
  "root": {
    "type": "Card",
    "props": { "title": "Hello" },
    "children": [{ "type": "Button", "props": { "label": "Click me" } }]
  }
}

Named Slots

Use `children` for the `"default"` slot. Use the element's top-level `slots` object for other slot names declared by the catalog:

{
  "type": "Layout",
  "props": {},
  "children": ["main"],
  "slots": {
    "header": ["heading"],
    "footer": ["actions"]
  }
}

Registry components receive named content as `slots?.header`, `slots?.footer`, and so on. Do not use `slots.default`.

Visibility Conditions

Use `visible` on elements to show/hide based on state. New syntax: `{ "$state": "/path" }`, `{ "$state": "/path", "eq": value }`, `{ "$state": "/path", "not": true }`, `{ "$and": [cond1, cond2] }` for AND, `{ "$or": [cond1, cond2] }` for OR. Helpers: `visibility.when("/path")`, `visibility.unless("/path")`, `visibility.eq("/path", val)`, `visibility.and(cond1, cond2)`, `visibility.or(cond1, cond2)`.

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 |

External Store (Controlled Mode)

Pass a `StateStore` to `StateProvider` (or `JSONUIProvider` / `createRenderer`) to use external state management (Redux, Zustand, XState, etc.):

import { createStateStore, type StateStore } from "@json-render/react";

const store = createStateStore({ count: 0 });

<StateProvider store={store}>{children}</StateProvider>;

// Mutate from anywhere — React re-renders automatically:
store.set("/count", 1);

When `store` is provided, `initialState` and `onStateChange` are ignored.

Dynamic Prop Expressions

Any prop value can be a data-driven expression resolved by the renderer before components receive props:

  • **`{ "$state": "/state/key" }`** - reads from state model (one-way read)
  • **`{ "$bindState": "/path" }`** - two-way binding: reads from state and enables write-back. Use on the natural value prop (value, checked, pressed, etc.) of form components.
  • **`{ "$bindItem": "field" }`** - two-way binding to a repeat item field. Use inside repeat scopes.
  • **Filtered lists**: `repeat` plus an `$item` visible condition on the same container renders only matching items: `{ "repeat": { "statePath": "/tasks", "key": "id" }, "visible": { "$item": "status", "eq": "todo" }, "children": ["task-card"] }`. AND-composed `$state` conjuncts gate the container shell; `$item`/`$index` conjuncts filter items.
  • **Nested lists**: inside a repeat, use `{ "repeat": { "statePath": { "$item": "comments" }, "key": "id" } }` to iterate an array on the enclosing item.
  • **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
  • **`{ "$template": "Hello, ${/name}!" }`** - interpolates state values into strings
  • **`{ "$computed": "fn", "args": { ... } }`** - calls registered functions with resolved args
{
  "type": "Input",
  "props": {
    "value": { "$bindState": "/form/email" },
    "placeholder": "Email"
  }
}

Components do not use a `statePath` prop for two-way binding. Use `{ "$bindState": "/path" }` on the natural value prop instead.

Components receive alread

Read more
Ships withjson-render

The Generative UI framework. Generate dynamic, personalized UIs from prompts without sacrificing reliability. Predefined components and actions for safe, predictable output.

Get the whole plugin
Stats
16,170
Stars
874
Forks
Active
Maintenance
TypeScript
Language
Apache-2.0
License
2d ago
Last commit
8mo ago
Created

Repo: vercel-labs/json-render

Other skills on json-render.