| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- export const metadata = { title: "Specs" }
- # 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](/docs/catalog) and can optionally follow a [schema](/docs/schemas). 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:
- ```json
- {
- "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:
- ```json
- {
- "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:
- ```json
- {
- "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:
- ```json
- {
- "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:
- ```json
- {
- "type": "ComponentName",
- "props": { "label": "Hello" },
- "children": ["child-1", "child-2"]
- }
- ```
- - `type` — Component type from your catalog
- - `props` — Component properties
- - `children` — 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:
- ```json
- {
- "type": "Metric",
- "props": {
- "label": "Total Revenue",
- "value": { "$state": "/metrics/revenue" },
- "change": { "$state": "/metrics/revenueChange" }
- },
- "children": []
- }
- ```
- See [Data Binding](/docs/data-binding) for the full reference including `$item`, `$index`, repeat, and two-way binding.
- ### Conditional Visibility
- Control when elements appear using the `visible` property:
- ```json
- {
- "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:
- ```typescript
- 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:
- ```tsx
- 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](/docs/api/react) for full provider and hook documentation.
- ### Streaming a Spec (React)
- With `@json-render/react`, use the `useUIStream` hook to stream specs incrementally:
- ```tsx
- 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](/docs/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](/docs/catalog) — the vocabulary of components available in your specs.
|