Specs
A spec is a JSON document that describes your UI.
What is a Spec?
A spec (specification) is the actual JSON that describes a UI. It uses components from a catalog and can optionally follow a schema. Specs can be:
- Generated by AI in real-time
- Stored in a database
- Streamed progressively from a server
- Hand-authored as JSON files
json-render is schema-agnostic — your specs can follow any JSON structure you choose.
Example Specs
Simple Spec
A basic spec using the @json-render/react schema. Note the flat structure with a root key and elements map:
{
"root": "card-1",
"elements": {
"card-1": {
"type": "Card",
"props": { "title": "Welcome" },
"children": ["text-1"]
},
"text-1": {
"type": "Text",
"props": { "content": { "$state": "/user/greeting" } },
"children": []
}
}
}Complex Spec
A more complex spec with multiple nested elements:
{
"root": "card-1",
"elements": {
"card-1": {
"type": "Card",
"props": { "title": "User Profile", "padding": "md" },
"children": ["row-1", "button-1"]
},
"row-1": {
"type": "Row",
"props": { "gap": "md" },
"children": ["avatar-1", "stack-1"]
},
"avatar-1": {
"type": "Avatar",
"props": { "src": { "$state": "/user/avatar" }, "alt": { "$state": "/user/name" } },
"children": []
},
"stack-1": {
"type": "Stack",
"props": { "gap": "sm" },
"children": ["name-text", "email-text"]
},
"name-text": {
"type": "Text",
"props": { "content": { "$state": "/user/name" }, "variant": "heading" },
"children": []
},
"email-text": {
"type": "Text",
"props": { "content": { "$state": "/user/email" }, "variant": "caption" },
"children": []
},
"button-1": {
"type": "Button",
"props": { "label": "Edit Profile" },
"children": []
}
}
}Block-Level Spec
A high-level spec using semantic blocks for page layouts:
{
"root": "page",
"elements": {
"page": {
"type": "Page",
"props": {},
"children": ["header", "hero", "features", "footer"]
},
"header": {
"type": "Header",
"props": { "logo": "/logo.svg", "navItems": ["Products", "Pricing", "Docs"] },
"children": []
},
"hero": {
"type": "Hero",
"props": {
"title": "Build UIs with JSON",
"subtitle": "Let AI generate your interfaces",
"ctaLabel": "Get Started",
"ctaHref": "/docs"
},
"children": []
},
"features": {
"type": "Features",
"props": { "columns": 3 },
"children": ["feature-1", "feature-2", "feature-3"]
},
"feature-1": {
"type": "Feature",
"props": { "icon": "zap", "title": "Fast", "description": "Render UIs in milliseconds" },
"children": []
},
"feature-2": {
"type": "Feature",
"props": { "icon": "shield", "title": "Secure", "description": "Validate all specs against your catalog" },
"children": []
},
"feature-3": {
"type": "Feature",
"props": { "icon": "sparkles", "title": "AI-Ready", "description": "Generate prompts from your catalog" },
"children": []
},
"footer": {
"type": "Footer",
"props": { "copyright": "2025 Acme Inc", "links": ["Privacy", "Terms", "Contact"] },
"children": []
}
}
}Spec Anatomy
Specs are schema-agnostic — the JSON structure is entirely up to you. The examples below use the root + elements flat tree format from the @json-render/react schema, which is optimized for AI generation and streaming.
Root and Elements
In the React schema, a spec has a root key pointing to the entry element, and an elements map containing all elements:
{
"root": "card-1",
"elements": {
"card-1": {
"type": "Card",
"props": { "title": "My Card" },
"children": ["text-1"]
},
"text-1": { ... }
}
}Element Structure
Each element in the map has a consistent shape:
{
"type": "ComponentName",
"props": { "label": "Hello" },
"children": ["child-1", "child-2"]
}type— Component type from your catalogprops— Component propertieschildren— Array of child element keys
Dynamic Data
Props can reference data from the state model using $state expressions. The value is a JSON Pointer (RFC 6901) path into the state:
{
"type": "Metric",
"props": {
"label": "Total Revenue",
"value": { "$state": "/metrics/revenue" },
"change": { "$state": "/metrics/revenueChange" }
},
"children": []
}See Data Binding for the full reference including $item, $index, repeat, and two-way binding.
Conditional Visibility
Control when elements appear using the visible property:
{
"type": "Alert",
"props": {
"message": "You have unsaved changes"
},
"children": [],
"visible": {
"$state": "/form/isDirty",
"eq": true
}
}Working with Specs
Validating a Spec
Use validateSpec from @json-render/core to check a spec for structural issues:
import { validateSpec } from '@json-render/core';
const result = validateSpec(spec);
if (!result.valid) {
console.error('Invalid spec:', result.issues);
}Rendering a Spec (React)
With @json-render/react, wrap the Renderer in providers to supply state and visibility:
import { Renderer, StateProvider, VisibilityProvider } from '@json-render/react';
import { registry } from './registry';
function MyApp({ spec, initialState }) {
return (
<StateProvider initialState={initialState}>
<VisibilityProvider>
<Renderer spec={spec} registry={registry} />
</VisibilityProvider>
</StateProvider>
);
}See the @json-render/react API reference for full provider and hook documentation.
Streaming a Spec (React)
With @json-render/react, use the useUIStream hook to stream specs incrementally:
import { useUIStream } from '@json-render/react';
function GenerativeUI() {
const { spec, isStreaming } = useUIStream({
api: '/api/generate',
});
return (
<Renderer
spec={spec}
registry={registry}
loading={isStreaming}
/>
);
}See Streaming for the full SpecStream format and server-side setup.
Spec Sources
Specs can come from various sources:
- AI Generation — LLMs generate specs based on prompts and catalog
- Database — Store specs as JSON and load dynamically
- API Response — Server returns specs based on user/context
- Static Files — Pre-built specs for known UI patterns
Next
Learn about catalogs — the vocabulary of components available in your specs.