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 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:

{
  "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.