| title | Architecture Overview |
|---|---|
| description | Understanding ObjectUI's architecture, design principles, and how the pieces fit together |
ObjectUI is a universal, server-driven UI (SDUI) engine built on React, Tailwind CSS, and Shadcn UI. This guide explains the core architecture and how all the pieces work together.
ObjectUI follows three fundamental principles:
- JSON-First: Every UI element is described as JSON metadata, not hardcoded React components
- Backend Agnostic: Works with any backend system (ObjectStack, custom APIs, etc.)
- Component Library Quality: Combines low-code speed with Shadcn/Tailwind design quality
┌─────────────────────────────────────────────┐
│ JSON Schema (Protocol) │ ← Backend sends this
├─────────────────────────────────────────────┤
│ @object-ui/react (Renderer) │ ← Interprets schema
├─────────────────────────────────────────────┤
│ Component Registry + Field Registry │ ← Lookup system
├─────────────────────────────────────────────┤
│ @object-ui/components (UI Primitives) │ ← Buttons, Cards, etc.
│ @object-ui/fields (Form Inputs) │ ← Text, Date, Select
│ @object-ui/layout (Page Structure) │ ← AppShell, Sidebar
│ @object-ui/plugin-* (Advanced Widgets) │ ← Grid, Charts, Kanban
├─────────────────────────────────────────────┤
│ Shadcn UI + Radix UI (Primitives) │ ← Accessible components
│ Tailwind CSS (Styling) │ ← Utility-first CSS
└─────────────────────────────────────────────┘
ObjectUI is organized as a PNPM monorepo with clear separation of concerns:
- Role: The Protocol
- Contains: Pure TypeScript interfaces for JSON schemas
- Constraint: ZERO dependencies, no React code
- Example:
ComponentSchema,ActionSchema,FieldSchema
- Role: The Engine
- Contains: Schema validation, expression evaluation, registries
- Constraint: No UI library dependencies, logic only
- Features:
- Expression engine (
visible: "${data.age > 18}") - Schema registry and validation
- Event system
- Expression engine (
- Role: The Runtime
- Contains:
SchemaRendererand React integration - Purpose: Transforms JSON schemas into live React components
- Role: The Atoms
- Contains: Shadcn primitives (Button, Badge, Card, Dialog, etc.)
- Constraint: Pure UI, no business logic
- Style: Tailwind CSS with
class-variance-authority
- Role: The Inputs
- Contains: Standard field renderers (Text, Number, Select, Date, etc.)
- Implements:
FieldWidgetPropsinterface - Purpose: Reusable form inputs with consistent API
- Role: The Shell
- Contains: Page structure components (AppShell, Page, Sidebar, Header)
- Purpose: Routing-aware composition and app scaffolding
Each plugin provides specialized, complex widgets:
@object-ui/plugin-grid- Data tables with ObjectStack integration@object-ui/plugin-kanban- Kanban board view@object-ui/plugin-charts- Recharts-based visualizations@object-ui/plugin-calendar- Calendar and event views@object-ui/plugin-map- Map visualization@object-ui/plugin-form- Advanced forms@object-ui/plugin-editor- Code editor (Monaco)@object-ui/plugin-markdown- Markdown renderer@object-ui/plugin-gantt- Gantt chart timeline@object-ui/plugin-timeline- Event timeline@object-ui/plugin-dashboard- Dashboard layouts@object-ui/plugin-chatbot- Chat interface
Important: Heavy dependencies (like Monaco, Recharts) are only allowed in plugin packages to keep the core bundle small.
Development tools and integration utilities:
@object-ui/cli- Command-line tool for building apps from schemas@object-ui/create-plugin- Interactive plugin scaffolder@object-ui/runner- Universal runtime for testing and demos@object-ui/data-objectstack- ObjectStack data backend adaptervscode-extension- VS Code extension for schema development
A backend system sends a JSON schema:
{
"type": "card",
"title": "Welcome",
"body": {
"type": "text",
"value": "Hello, ${user.name}!"
}
}The SchemaRenderer component:
- Receives the schema + data context
- Evaluates expressions (
${user.name}) - Looks up the component type in the registry
- Recursively renders child schemas
- Handles events and state updates
import { SchemaRenderer } from '@object-ui/react'
function App() {
const data = { user: { name: "Alice" } }
return <SchemaRenderer schema={schema} data={data} />
}The registry maps type strings to React components:
// During app initialization
ComponentRegistry.register('card', CardComponent)
ComponentRegistry.register('text', TextComponent)
// At runtime
const Component = ComponentRegistry.get('card') // → CardComponentThe registered component renders with evaluated props:
<CardComponent title="Welcome">
<TextComponent value="Hello, Alice!" />
</CardComponent>ObjectUI uses two registry systems for extensibility:
Maps schema types to React components:
import { ComponentRegistry } from '@object-ui/core'
// Register a component
ComponentRegistry.register('my-widget', MyWidgetComponent, {
label: 'My Widget',
category: 'Custom',
icon: 'box',
inputs: [
{ name: 'title', type: 'string', label: 'Title' }
]
})Maps field types to input components:
import { registerFieldRenderer } from '@object-ui/fields'
// Register a field renderer
registerFieldRenderer('rating', RatingFieldComponent)This allows:
- ✅ Overriding standard components
- ✅ Adding custom field types
- ✅ Plugin system for complex widgets
- ✅ Keeping bundles small (lazy loading)
ObjectUI includes a powerful expression engine for dynamic UIs:
{
"type": "text",
"value": "Welcome, ${user.firstName} ${user.lastName}!"
}{
"type": "button",
"text": "Submit",
"visible": "${form.isValid && !form.isSubmitting}",
"disabled": "${form.isSubmitting}"
}{
"type": "badge",
"text": "${orders.length} Orders",
"variant": "${orders.length > 10 ? 'success' : 'warning'}"
}See the Expressions Guide for complete details.
Backend API
↓
JSON Schema + Data
↓
SchemaRenderer (evaluates expressions)
↓
Component Registry (maps types)
↓
React Components (render UI)
↓
User Interactions (events)
↓
Event Handlers (update data)
↓
Re-render (React state updates)
ObjectUI uses Tailwind CSS exclusively for styling:
All component variants use cva for type-safe variants:
import { cva } from 'class-variance-authority'
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 px-3',
lg: 'h-11 px-8',
}
}
}
)Use cn() helper (tailwind-merge + clsx) for class overrides:
import { cn } from '@/lib/utils'
<Button className={cn('default-classes', props.className)} />- Inline styles (
style={{}}) - except for dynamic values - CSS Modules
- Styled-components
- Any CSS-in-JS library
ObjectUI is built with TypeScript in strict mode:
import type { ComponentSchema, ButtonSchema } from '@object-ui/types'
const schema: ButtonSchema = {
type: 'button',
text: 'Click me',
variant: 'default', // ✅ Type-checked
onClick: 'handleClick'
}- Unit Tests: Vitest + React Testing Library
- Type Tests: TypeScript strict mode
Heavy dependencies only go in plugins:
- ✅
@object-ui/plugin-chartscan import Recharts - ❌
@object-ui/componentscannot import Recharts
Don't import components directly - use registries:
// ❌ Bad
import { MyGrid } from './MyGrid'
<MyGrid data={data} />
// ✅ Good
ComponentRegistry.register('my-grid', MyGrid)
{ type: 'my-grid', data: [...] }Never use inline styles or CSS-in-JS:
// ❌ Bad
<div style={{ backgroundColor: 'red' }}>
// ✅ Good
<div className="bg-red-500">Use expressions for dynamic content:
// ❌ Bad - hardcoded
{ type: 'text', value: 'Hello, John!' }
// ✅ Good - dynamic
{ type: 'text', value: 'Hello, ${user.name}!' }When creating a plugin:
- Create package in
packages/plugin-{name}/ - Export components and types
- Register components with ComponentRegistry
- Add documentation in
content/docs/plugins/ - Add to plugins meta.json
// packages/plugin-mywidget/src/index.tsx
import { ComponentRegistry } from '@object-ui/core'
import { MyWidget } from './MyWidget'
export { MyWidget }
ComponentRegistry.register('my-widget', MyWidget, {
label: 'My Widget',
category: 'Plugins',
icon: 'box'
})- Schema Rendering - How schemas become React components
- Component Registry - Registering custom components
- Field Registry - Custom field types
- Expressions - Dynamic expressions
- Plugins - Plugin system
- Data Sources - Data integration
- Utilities - Development tools and CLI utilities