Server-Driven UI template library for React — the server defines UI structure as JSON; the client renders it with type-safe, subscription-based React components.
Quick start · Packages · MCP & AI assistants · Philosophy · Architecture · Document model · Development
The server decides what to show; the client decides how to render it through an explicit component registry. Built for dashboard builders, dynamic forms, CMS pages, and A/B layouts in React/Next.js apps — change UI structure without redeploying the client.
Server / CMS / Builder
│ SduiLayoutDocument (JSON)
▼
SduiLayoutRenderer + registered ComponentFactory map
▼
Subscription-based React UI (only changed nodes re-render)
| ① server JSON | → | ② renderer | → | ③ interactive UI |
|---|
① Server JSON — nested 3-level tree (Container → Card → Counter):
{
"version": "1.0.0",
"root": {
"id": "page",
"type": "Container",
"children": [
{
"id": "card",
"type": "Card",
"state": { "title": "Dashboard" },
"children": [{ "id": "counter", "type": "Counter", "state": { "label": "Clicks", "count": 0 } }]
}
]
}
}② Renderer + ③ interactive UI — register factories; containers recurse with useRenderNode:
'use client'
import {
SduiLayoutRenderer,
useSduiNodeSubscription,
useSduiLayoutAction,
useRenderNode,
type SduiLayoutDocument,
} from '@lodado/sdui-template'
import { z } from 'zod'
const counterSchema = z.object({ label: z.string(), count: z.number().default(0) })
const cardSchema = z.object({ title: z.string() })
function Container({ id }: { id: string }) {
const { childrenIds } = useSduiNodeSubscription({ nodeId: id })
const { renderChildren } = useRenderNode({ nodeId: id })
return <section>{renderChildren(childrenIds)}</section>
}
function Card({ id }: { id: string }) {
const { state, childrenIds } = useSduiNodeSubscription({ nodeId: id, schema: cardSchema })
const { renderChildren } = useRenderNode({ nodeId: id })
return (
<article>
<h2>{state.title}</h2>
{renderChildren(childrenIds)}
</article>
)
}
function Counter({ id }: { id: string }) {
const { state } = useSduiNodeSubscription({ nodeId: id, schema: counterSchema })
const store = useSduiLayoutAction()
return (
<button type="button" onClick={() => store.updateNodeState(id, { count: state.count + 1 })}>
{state.label}: {state.count}
</button>
)
}
const document: SduiLayoutDocument = {
version: '1.0.0',
root: {
id: 'page',
type: 'Container',
children: [
{
id: 'card',
type: 'Card',
state: { title: 'Dashboard' },
children: [{ id: 'counter', type: 'Counter', state: { label: 'Clicks', count: 0 } }],
},
],
},
}
export default function Page() {
return (
<SduiLayoutRenderer
document={document}
components={{
Container: (id) => <Container id={id} />,
Card: (id) => <Card id={id} />,
Counter: (id) => <Counter id={id} />,
}}
/>
)
}- At a glance
- Installation
- Quick start
- Packages
- MCP & AI assistants
- Philosophy
- Architecture
- Document model
- Node references
- Component package
- Development
- When to use
| Capability | Description |
|---|---|
| Server-Driven UI | UI tree expressed as SduiLayoutDocument JSON |
| React renderer | SduiLayoutRenderer resolves registered component factories |
| Typed state | Per-node state validated and inferred with Zod |
| Node subscriptions | Only changed nodes re-render — not the full tree |
| Node references | Nodes subscribe to other nodes' state changes |
| Recursive rendering | Nested UI via children and useRenderNode |
| Next.js friendly | Works as client components in App Router |
| Monorepo | Core renderer, components, design tokens, and document editor split into packages |
pnpm add @lodado/sdui-template zod@^4.3.6
# or
npm install @lodado/sdui-template zod@^4.3.6With default UI components and design tokens:
pnpm add @lodado/sdui-template-component @lodado/sdui-design-filesThis repo targets Zod v4. Use a compatible version in your app.
'use client'
import { SduiLayoutRenderer, type ComponentFactory, type SduiLayoutDocument } from '@lodado/sdui-template'
const document: SduiLayoutDocument = {
version: '1.0.0',
root: {
id: 'root-card',
type: 'Card',
state: {
title: 'Hello SDUI',
body: 'This UI came from a JSON document.',
},
},
}
const CardFactory: ComponentFactory = (id) => <Card id={id} />
function Card({ id }: { id: string }) {
return <article data-node-id={id}>Card node: {id}</article>
}
export default function Page() {
return <SduiLayoutRenderer document={document} components={{ Card: CardFactory }} />
}The server owns document. The client maps type: 'Card' to components.Card.
| Package | npm | Role |
|---|---|---|
@lodado/sdui-template |
SDUI renderer, store, hooks, schema, normalization | |
@lodado/sdui-template-component |
Radix UI component map (sduiComponents) |
|
@lodado/sdui-design-files |
Design tokens and CSS variables (Atlassian DS) | |
@lodado/sdui-document |
Headless block document domain, patches, permissions | |
@lodado/sdui-document-react |
Notion-like block editor (React + ProseMirror) | |
@lodado/sdui-mcp |
MCP server — compressed SDUI authoring knowledge | |
ssr-testing |
— | Next.js SSR + Playwright E2E integration testbed |
apps/docs |
— | Storybook documentation (port 6006) |
Which package do I need?
- Layout JSON → React components →
@lodado/sdui-template - Ready-made Button/Dialog/Form map →
@lodado/sdui-template-component - Block document editing →
@lodado/sdui-document-react - Document domain only (no React) →
@lodado/sdui-document - AI assistant pulls component guides at runtime →
@lodado/sdui-mcp
This monorepo ships @lodado/sdui-mcp — an MCP (Model Context Protocol) server that feeds compressed authoring knowledge to AI coding tools. It covers SDUI layout JSON (@lodado/sdui-template + @lodado/sdui-template-component). For block documents (@lodado/sdui-document, @lodado/sdui-document-react), use AGENTS.md and package READMEs.
- Node.js 24+ (same as CI)
- An MCP-capable client: Cursor, Claude Code, Claude Desktop, Windsurf, Cline, etc.
Option A — UI (recommended)
- Open Cursor Settings → MCP (or Features → MCP)
- Click Add MCP Server
- Name:
sdui - Command:
npx - Args:
-y,@lodado/sdui-mcp - Save and confirm the server shows connected (green)
Option B — project config (share with your team)
Create .cursor/mcp.json in your app repo root:
{
"mcpServers": {
"sdui": {
"command": "npx",
"args": ["-y", "@lodado/sdui-mcp"]
}
}
}Restart Cursor or reload MCP servers from settings.
One command:
claude mcp add sdui -- npx -y @lodado/sdui-mcpOr commit .mcp.json at the repo root (same JSON shape as Cursor above) so the team shares one config.
Edit claude_desktop_config.json:
| OS | Config path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
Add under mcpServers:
{
"mcpServers": {
"sdui": {
"command": "npx",
"args": ["-y", "@lodado/sdui-mcp"]
}
}
}Restart Claude Desktop.
Ask your assistant:
List SDUI components using the sdui MCP server.
It should call sdui_list_components and return component names (Button, Dialog, Card, …). If tools are missing:
- Confirm Node 24+ is on your
PATHin the IDE's environment - Run manually:
npx -y @lodado/sdui-mcp— should start without errors (Ctrl+C to stop) - Reload MCP in your client after config changes
| Tool | When to use |
|---|---|
sdui_list_components |
Discover available @lodado/sdui-template-component entries |
sdui_get_guide |
Fetch syntax, architecture, types, components-overview, or a component name |
sdui_get_examples |
Real Storybook SduiLayoutDocument JSON for a component |
sdui_get_snapshot |
Delta-sync knowledge into .ai/sdui/ in consumer repos |
Prompt: sdui-author-document — guided SDUI layout JSON authoring (not block documents).
Resource: sdui://knowledge/{path} — direct access to each knowledge file.
Full details: packages/sdui-mcp/README.md
Best for one-off SDUI layout tasks:
User: "Build an SDUI document with a Dialog confirming account deletion."
Assistant calls:
sdui_get_guide { topic: "syntax" }
sdui_get_guide { topic: "Dialog" }
sdui_get_examples { component: "Dialog" }
→ writes SduiLayoutDocument JSON following those patterns
For frequent SDUI work, sync knowledge locally so the assistant reads files instead of calling MCP every task:
mkdir -p .claude/skills/sdui-sync
cp node_modules/@lodado/sdui-mcp/consumer/sdui-sync/SKILL.md .claude/skills/sdui-sync/SKILL.mdRun /sdui-sync in Claude Code. It writes .ai/sdui/ (syntax, components, examples + manifest) and re-syncs only changed files when older than 7 days.
| Task | Read first |
|---|---|
| SDUI layout JSON + components | MCP tools above |
| Block document domain (patches, permissions) | packages/sdui-document/README.md |
| Block editor UI (React) | packages/sdui-document-react/README.md |
| End-to-end AI workflows, block types, checklists | AGENTS.md · docs/AI-ASSISTANT-GUIDE.md |
| Monorepo conventions for agents | CLAUDE.md · AGENTS.md |
| Live examples | pnpm storybook → apps/docs (port 6006) |
Storybook paths for block documents:
Document/Adapter—toSduiLayoutDocument+ rendererDocument/Catalog— every block typeDocumentEditor— interactive editor stories
SDUI documents are data — component names, state, children, and references. No arbitrary code over the wire.
type maps to an explicit components registry. The server drives layout; the client controls rendering authority.
| Field | Purpose |
|---|---|
state |
Component data and behavior |
attributes |
Style, className, HTML-like props |
children |
Nested UI |
reference |
Other node IDs to read/subscribe |
SduiLayoutStore + SubscriptionManager propagate node-level changes instead of re-rendering the entire tree.
@lodado/sdui-template is the rendering engine. Bring your own design system, or use @lodado/sdui-template-component for a fast start.
Server / CMS / Builder
│
│ SduiLayoutDocument (JSON)
▼
┌──────────────────────────────────────────┐
│ @lodado/sdui-template │
│ React layer: Renderer, Provider, hooks │
│ Store layer: SduiLayoutStore, subs │
│ Data layer: schema, normalize/denormalize│
└──────────────────────────────────────────┘
│ ComponentFactory(id, parentPath)
▼
Consumer React Components
1. Server/builder creates SduiLayoutDocument
2. SduiLayoutRenderer builds SduiLayoutStore
3. Document normalizes into id-keyed storage
4. renderNode walks from root
5. ComponentFactory resolved by type (with override priority)
6. useSduiNodeSubscription reads node state
7. store.updateNodeState patches a node
8. SubscriptionManager notifies only that node's subscribers
componentOverrides.byNodeId[node.id]componentOverrides.byNodeType[node.type]components[node.type]defaultComponentFactory
interface SduiLayoutDocument {
version: string
metadata?: { id?: string; name?: string; description?: string; [key: string]: unknown }
root: SduiLayoutNode
variables?: Record<string, unknown>
}
interface SduiLayoutNode {
id: string
type: string
state?: Record<string, unknown>
attributes?: Record<string, unknown>
children?: SduiLayoutNode[]
reference?: string | string[]
}Rules:
idmust be unique within the documenttypemust match a registered component key- Put component data in
state, presentation props inattributes - Nest UI with
children; link nodes withreference
const document = {
version: '1.0.0',
root: {
id: 'root',
type: 'Container',
children: [
{ id: 'toggle', type: 'Toggle', state: { checked: false, label: 'Enable' } },
{ id: 'status', type: 'StatusText', reference: 'toggle' },
],
},
}import { useSduiNodeReference } from '@lodado/sdui-template'
function StatusText({ id }: { id: string }) {
const { referencedNodesMap } = useSduiNodeReference({ nodeId: id })
const toggle = referencedNodesMap['toggle']
return <p>Status: {toggle?.state.checked ? 'ON' : 'OFF'}</p>
}Supports string or string[] references. Access via referencedNodesMap[id] (O(1)) or iterate referencedNodes.
'use client'
import { SduiLayoutRenderer } from '@lodado/sdui-template'
import { sduiComponents } from '@lodado/sdui-template-component'
import '@lodado/sdui-design-files/index.css'
export default function Page({ document }) {
return <SduiLayoutRenderer document={document} components={sduiComponents} />
}See packages/sdui-template-component/README.md for the full component map.
pnpm workspace + Turborepo:
pnpm install
pnpm dev # parallel dev across packages
pnpm storybook # Storybook on port 6006
pnpm build
pnpm test
pnpm typecheck
pnpm lintPer-package:
pnpm --filter @lodado/sdui-template test
pnpm --filter @lodado/sdui-document build- Admin dashboards or layouts configured from JSON
- Dynamic forms, cards, and lists driven by server config
- CMS/builder output rendered in a React app
- Per-node overrides for A/B tests or tenant-specific UI
- Design-system components with server-controlled layout decisions
- Data fetching, auth, or persistence policies (app responsibility)
- Final accessibility guarantees (component responsibility)
- Arbitrary HTML or server-delivered function execution
MIT