Schemas
Schemas define the structure and validation rules for your UI specs.
What is a Schema?
A schema defines the JSON structure that describes your UI. It includes:
- Element structure — How components are nested and referenced
- Property types — What props each component accepts
- Data binding syntax — How to reference dynamic data
- Action format — How user interactions are defined
Schema-Agnostic by Design
json-render can work with any JSON schema. @json-render/core provides the primitives to define catalogs and renderers for any format:
- @json-render/react — The built-in flat element tree schema
- A2UI — Google's Agent-to-User Interaction protocol
- Adaptive Cards — Microsoft's platform-agnostic UI format
- AG-UI — CopilotKit's Agent User Interaction Protocol
- OpenAPI/Swagger — API documentation schemas for dynamic forms
- Custom schemas — Design your own format tailored to your domain
See the Custom Schema guide to learn how to implement support for any schema.
Built-in Schema
@json-render/react uses a flat element tree schema with a root key and elements map:
{
"root": "card-1",
"elements": {
"card-1": {
"type": "Card",
"props": { "title": "Dashboard" },
"children": ["text-1", "button-1"]
},
"text-1": {
"type": "Text",
"props": { "content": { "$state": "/user/name" } },
"children": []
},
"button-1": {
"type": "Button",
"props": { "label": "Click me" },
"children": []
}
}
}Schema Components
Element Structure
In the built-in schema, each element in the elements map has this structure:
interface Element {
type: string; // Component type from catalog
props: Record<string, any>; // Component properties
children: string[]; // Array of child element keys
visible?: VisibilityCondition; // Conditional display
}Data Binding Syntax
Reference dynamic data using $state expressions in props. The value is a JSON Pointer path into the state model:
{
"type": "Text",
"props": {
"content": { "$state": "/user/name" },
"count": { "$state": "/items/count" }
},
"children": []
}json-render also supports $item and $index expressions for lists, two-way binding via $bindState / $bindItem, and conditional props. See Data Binding for the full reference.
Action Format
Actions are defined in the catalog and referenced from components. The renderer handles action execution:
// In your catalog
actions: {
navigate: {
params: z.object({ url: z.string() }),
description: 'Navigate to a URL',
},
apiCall: {
params: z.object({
endpoint: z.string(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
}),
description: 'Make an API request',
},
}Custom Schemas
@json-render/core is schema-agnostic. You can define any JSON structure:
import { z } from 'zod';
// Define your own element schema
const MyElementSchema = z.object({
component: z.string(),
settings: z.record(z.unknown()),
nested: z.array(z.lazy(() => MyElementSchema)).optional(),
});
// Define your own data binding format
const BoundValue = z.object({
literal: z.string().optional(),
source: z.string().optional(), // e.g., "/users/0/name"
});
// Define your own action format
const ActionSchema = z.object({
name: z.string(),
context: z.record(z.unknown()).optional(),
});Schema vs Catalog
The schema and catalog work together but serve different purposes:
- Schema — Defines the JSON structure (how elements are organized)
- Catalog — Defines available components and their props (what can be used)
The schema is the grammar; the catalog is the vocabulary.
Next
Learn about specs — the actual JSON documents that describe your UI.