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"],
"slots": {
"header": ["heading-1"],
"footer": ["actions-1"]
}
}type— Component type from your catalogprops— Component propertieschildren— Array of child element keysslots: Optional map of named slots to child element keys. Usechildrenfor the default slot. Named slot rendering is currently supported by@json-render/react.
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.