
React
- 4.9k installs
- 15.8k repo stars
- Updated July 8, 2026
- vercel-labs/json-render
A React renderer library that converts JSON element tree specifications into rendered React component trees using a type-safe catalog system with state management.
About
React renderer that transforms JSON specs into React component trees using a catalog-based architecture. Developers use it when building dynamic UIs from JSON, creating AI-generated component specs, or developing component catalogs without writing JSX directly. Key workflows include defining typed component catalogs with Zod schemas, implementing state management via StateProvider with optional external stores (Redux, Zustand), enabling two-way prop binding via $bindState expressions, and handling events through an action system. Built-in actions (setState, pushState, removeState, validateForm) manage state mutations automatically. Supports visibility conditions, computed expressions, state watchers, and validation providers for form fields. Define type-safe component catalogs with Zod schema validation for props Two-way binding via $bindState on component value props with useBoundProp hook Built-in state actions (setState, pushState, removeState, validateForm) require no catalog declaration External state store integration for Redux, Zustand, XState via StateStore interface Event system with action dispatching, visibility conditions, and state watchers on elements Convert JSON el.
- Define type-safe component catalogs with Zod schema validation for props
- Two-way binding via $bindState on component value props with useBoundProp hook
- Built-in state actions (setState, pushState, removeState, validateForm) require no catalog declaration
- External state store integration for Redux, Zustand, XState via StateStore interface
- Event system with action dispatching, visibility conditions, and state watchers on elements
React by the numbers
- 4,892 all-time installs (skills.sh)
- +429 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #100 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)
react capabilities & compatibility
free
- Capabilities
- convert json element trees to react components · type safe prop validation via zod schemas · two way state binding ($bindstate, $binditem exp · action dispatching and event handling · conditional rendering with visibility rules · external state store integration · form validation via validationprovider · computed expressions and template interpolation
- Use cases
- frontend · ui design · api development
- Platforms
- macOS · Windows · Linux · WSL
- Runs
- Runs locally
- Pricing
- Free
What react says it does
React renderer that converts JSON specs into React component trees.
npx skills add https://github.com/vercel-labs/json-render --skill reactAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4.9k |
|---|---|
| repo stars | ★ 15.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 8, 2026 |
| Repository | vercel-labs/json-render ↗ |
What it does
Convert JSON specifications into type-safe React component trees with state management, two-way binding, and event handling.
Who is it for?
AI-generated UI specs, dynamic component catalogs, form-heavy apps with JSON-driven layouts, design systems that need JSON serialization.
Skip if: Static pages, apps that require custom JSX, low-latency real-time UIs without state abstraction.
When should I use this skill?
You need to render React UIs from JSON specs, integrate external state stores, or build JSON-serializable component trees.
What you get
Type-safe React components rendered from JSON with full state control, event handling, validation, and external state management integration.
- Type-safe component registry
- Rendered React component tree
- State management context
By the numbers
- 5 built-in actions included (setState, pushState, removeState, validateForm, plus custom)
- 10+ key exports (defineRegistry, Renderer, useStateStore, useBoundProp, createStateStore, etc.)
- 4 provider types (StateProvider, ActionProvider, VisibilityProvider, ValidationProvider)
Files
@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() }),
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>
),
},
});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" } }
]
}
}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:
repeatplus an$itemvisible condition on the same container renders only matching items:{ "repeat": { "statePath": "/tasks", "key": "id" }, "visible": { "$item": "status", "eq": "todo" }, "children": ["task-card"] }. AND-composed$stateconjuncts gate the container shell;$item/$indexconjuncts filter items. - `{ "$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 already-resolved props. For two-way bound props, use the useBoundProp hook with the bindings map the renderer provides.
Register $computed functions via the functions prop on JSONUIProvider or createRenderer:
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>Event System
Components use emit to fire named events, or on() to get an event handle with metadata. The element's on field maps events to action bindings:
// 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" } }
}The EventHandle returned by on() has: emit(), shouldPreventDefault (boolean), and bound (boolean).
State Watchers
Elements can declare a watch field (top-level, sibling of type/props/children) to trigger actions when state values change:
{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}Built-in Actions
The setState, pushState, removeState, and validateForm actions are built into the React schema and handled automatically by ActionProvider. They are injected into AI prompts without needing to be declared in catalog actions:
{ "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 validates all registered fields and writes { valid, errors } to state.
Note: statePath in action params (e.g. setState.statePath) targets the mutation path. Two-way binding in component props uses { "$bindState": "/path" } on the value prop, not statePath.
useBoundProp
For form components that need two-way binding, use useBoundProp with the bindings map the renderer provides when a prop uses { "$bindState": "/path" } or { "$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) returns [value, setValue]. The value is the resolved prop; setValue writes back to the bound state path (no-op if not bound).
BaseComponentProps
For building reusable component libraries not tied to a specific catalog (e.g. @json-render/shadcn):
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);defineRegistry
defineRegistry conditionally requires the actions field only when the catalog declares actions. Catalogs with actions: {} can omit it.
Key Exports
| Export | Purpose |
|---|---|
defineRegistry | Create a type-safe component registry from a catalog |
Renderer | Render a spec using a registry |
schema | Element tree schema (includes built-in state actions: setState, pushState, removeState, validateForm) |
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 (returns null if no provider) |
useUIStream | Stream specs from an API endpoint |
createStateStore | Create a framework-agnostic in-memory StateStore |
StateStore | Interface for plugging in external state management |
BaseComponentProps | Catalog-agnostic base type for reusable component libraries |
EventHandle | Event handle type (emit, shouldPreventDefault, bound) |
ComponentContext | Typed component context (catalog-aware) |
Related skills
How it compares
Similar to form developers or low-code platforms, but JSON-first and headless; integrates into React apps rather than providing hosted UI editor.
FAQ
Do I need to define actions in the catalog if I use built-in actions?
No. setState, pushState, removeState, and validateForm are built into the React schema and handled automatically by ActionProvider without catalog declaration.
How do I enable two-way binding on form fields?
Use { "$bindState": "/path" } on the component's natural value prop (value, checked, etc.), then use useBoundProp hook in the component implementation to read and write back.
Can I use an external state store like Redux?
Yes. Pass a StateStore to StateProvider (or createRenderer). The store interface is framework-agnostic; set values via store.set("/path", value) and React re-renders automatically.
Is React safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.