| title | Schema Rendering |
|---|
Object UI's schema rendering system is the core mechanism that transforms JSON configurations into live React components. This guide explains how it works and how to use it effectively.
The schema rendering engine follows a simple principle:
JSON Schema → SchemaRenderer → React Components → Beautiful UI
Every visual element in Object UI starts as a JSON object that describes what should be rendered, not how it should be rendered.
The SchemaRenderer is the primary component that interprets your JSON schemas:
import { SchemaRenderer } from '@object-ui/react'
import { registerDefaultRenderers } from '@object-ui/components'
// Register components once at app initialization
registerDefaultRenderers()
function App() {
const schema = {
type: "page",
title: "My Dashboard",
body: { /* ... */ }
}
return <SchemaRenderer schema={schema} />
}Every schema object must have at minimum a type field:
interface BaseSchema {
type: string // Component type identifier
id?: string // Optional unique identifier
className?: string // Tailwind CSS classes
style?: CSSProperties // Inline styles (use sparingly)
visibleOn?: string // Expression for conditional visibility
hiddenOn?: string // Expression for conditional hiding
disabledOn?: string // Expression for conditional disabling
}{
"type": "card",
"id": "stats-card",
"className": "p-6 shadow-lg",
"title": "User Statistics",
"visibleOn": "${user.role === 'admin'}",
"body": {
"type": "text",
"value": "Total Users: ${stats.totalUsers}"
}
}The SchemaRenderer accepts a data prop that provides context for expressions:
const data = {
user: { name: "John", role: "admin" },
stats: { totalUsers: 1234 }
}
<SchemaRenderer schema={schema} data={data} />Use expression syntax ${} to reference data:
{
"type": "text",
"value": "Welcome, ${user.name}!"
}The schema renderer uses a component registry to map schema types to React components:
import { getComponentRegistry } from '@object-ui/react'
const registry = getComponentRegistry()
// Register a custom component
registry.register('my-component', MyComponent)
// Now you can use it in schemas
const schema = {
type: "my-component",
// ... component props
}Schemas can be nested to create complex UIs:
{
"type": "page",
"title": "Dashboard",
"body": {
"type": "grid",
"columns": 2,
"items": [
{
"type": "card",
"title": "Card 1",
"body": {
"type": "text",
"value": "Nested content"
}
},
{
"type": "card",
"title": "Card 2",
"body": {
"type": "chart",
"chartType": "bar",
"data": "${chartData}"
}
}
]
}
}Use arrays for multiple items:
{
"type": "container",
"body": [
{ "type": "text", "value": "First item" },
{ "type": "text", "value": "Second item" },
{ "type": "text", "value": "Third item" }
]
}Object UI includes a powerful expression system for dynamic behavior:
{
"type": "text",
"value": "${user.firstName} ${user.lastName}"
}{
"type": "badge",
"text": "${status === 'active' ? 'Active' : 'Inactive'}",
"variant": "${status === 'active' ? 'success' : 'default'}"
}{
"type": "button",
"label": "Delete",
"visibleOn": "${user.role === 'admin'}"
}{
"type": "alert",
"message": "Welcome!",
"variant": "${
user.isNew ? 'info' :
user.tasks.length === 0 ? 'warning' :
'success'
}"
}Components can emit events that you handle in React:
<SchemaRenderer
schema={schema}
onAction={(action, context) => {
console.log('Action:', action)
console.log('Context:', context)
}}
onSubmit={(data) => {
console.log('Form submitted:', data)
}}
/>Reference actions in schemas:
{
"type": "button",
"label": "Click Me",
"onClick": {
"actionType": "ajax",
"api": "/api/action"
}
}Large schemas are automatically optimized:
{
"type": "tabs",
"lazyLoad": true,
"tabs": [
{ "title": "Tab 1", "body": { /* Loaded when tab is clicked */ } },
{ "title": "Tab 2", "body": { /* Loaded when tab is clicked */ } }
]
}The renderer automatically memoizes components to prevent unnecessary re-renders.
Use dynamic imports for heavy components:
import { lazy } from 'react'
const HeavyChart = lazy(() => import('./HeavyChart'))
registry.register('heavy-chart', HeavyChart)The renderer includes built-in error boundaries:
<SchemaRenderer
schema={schema}
onError={(error, errorInfo) => {
console.error('Rendering error:', error)
// Log to error tracking service
}}
/>Full type safety for your schemas:
import type { PageSchema, FormSchema } from '@object-ui/core'
const schema: PageSchema = {
type: "page",
title: "Typed Page",
body: {
type: "form",
// TypeScript will validate this entire structure
body: [
// ...
]
}
}Break complex UIs into smaller, reusable schemas:
// ❌ Bad: One massive schema
const massiveSchema = { /* 500 lines of JSON */ }
// ✅ Good: Composed schemas
const headerSchema = { /* ... */ }
const contentSchema = { /* ... */ }
const footerSchema = { /* ... */ }
const pageSchema = {
type: "page",
body: [headerSchema, contentSchema, footerSchema]
}Pass all necessary data upfront:
// ✅ Good
const data = {
user: userData,
settings: userSettings,
stats: dashboardStats
}
<SchemaRenderer schema={schema} data={data} />Move logic to expressions instead of creating conditional schemas:
// ❌ Bad
const schema = user.isAdmin ? adminSchema : userSchema
// ✅ Good
const schema = {
type: "page",
body: [
{
type: "admin-panel",
visibleOn: "${user.isAdmin}"
},
{
type: "user-panel",
visibleOn: "${!user.isAdmin}"
}
]
}Always type your schemas for better IDE support and fewer runtime errors.
{
"type": "container",
"body": {
"type": "spinner",
"visibleOn": "${loading}"
}
}{
"type": "empty-state",
"visibleOn": "${items.length === 0}",
"message": "No items found",
"action": {
"type": "button",
"label": "Create New"
}
}{
"type": "alert",
"variant": "error",
"visibleOn": "${error}",
"message": "${error.message}"
}- Component Registry - Learn about component registration
- Expression System - Master expressions
- Protocol Overview - Explore all available schemas
- Schema Specification - Technical specification
- Architecture - System architecture
- Core API - Core package API reference
- React API - React package API reference