Loading...
Loading...
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.
npx skill4agent add vercel-labs/json-render reactimport { 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} />;
}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() }),
description: "Card container with title",
},
},
});
// 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>
),
},
});{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [
{ "type": "Button", "props": { "label": "Click me" } }
]
}
}visible{ "$state": "/path" }{ "$state": "/path", "eq": value }{ "$state": "/path", "not": true }{ "$and": [cond1, cond2] }{ "$or": [cond1, cond2] }visibility.when("/path")visibility.unless("/path")visibility.eq("/path", val)visibility.and(cond1, cond2)visibility.or(cond1, cond2)| Provider | Purpose |
|---|---|
| Share state across components (JSON Pointer paths). Accepts optional |
| Handle actions dispatched via the event system |
| Enable conditional rendering based on state |
| Form field validation |
StateStoreStateProviderJSONUIProvidercreateRendererimport { 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);storeinitialStateonStateChange{ "$state": "/state/key" }{ "$bindState": "/path" }{ "$bindItem": "field" }{ "$cond": <condition>, "$then": <value>, "$else": <value> }{ "$template": "Hello, ${/name}!" }{ "$computed": "fn", "args": { ... } }{
"type": "Input",
"props": {
"value": { "$bindState": "/form/email" },
"placeholder": "Email"
}
}statePath{ "$bindState": "/path" }useBoundPropbindings$computedfunctionsJSONUIProvidercreateRenderer<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>emiton()on// Simple event firing
Button: ({ props, emit }) => (
<button onClick={() => emit("press")}>{props.label}</button>
),
// Event handle with metadata (e.g. preventDefault)
Link: ({ props, on }) => {
const click = on("click");
return (
<a href={props.href} onClick={(e) => {
if (click.shouldPreventDefault) e.preventDefault();
click.emit();
}}>{props.label}</a>
);
},{
"type": "Button",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } }
}EventHandleon()emit()shouldPreventDefaultboundwatch{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}setStatepushStateremoveStatevalidateFormActionProvideractions{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
{ "action": "validateForm", "params": { "statePath": "/formResult" } }validateForm{ valid, errors }statePathsetState.statePath{ "$bindState": "/path" }statePathuseBoundPropbindings{ "$bindState": "/path" }{ "$bindItem": "field" }import { useBoundProp } from "@json-render/react";
Input: ({ element, bindings }) => {
const [value, setValue] = useBoundProp<string>(
element.props.value,
bindings?.value
);
return (
<input
value={value ?? ""}
onChange={(e) => setValue(e.target.value)}
/>
);
},useBoundProp(propValue, bindingPath)[value, setValue]valuesetValue@json-render/shadcnimport type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);defineRegistryactionsactions: {}| Export | Purpose |
|---|---|
| Create a type-safe component registry from a catalog |
| Render a spec using a registry |
| Element tree schema (includes built-in state actions: setState, pushState, removeState, validateForm) |
| Access state context |
| Get single value from state |
| Two-way binding for |
| Access actions context |
| Get a single action dispatch function |
| Non-throwing variant of useValidation (returns null if no provider) |
| Stream specs from an API endpoint |
| Create a framework-agnostic in-memory |
| Interface for plugging in external state management |
| Catalog-agnostic base type for reusable component libraries |
| Event handle type ( |
| Typed component context (catalog-aware) |