Skip to content

Latest commit

 

History

458 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@lodado/sdui-template

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.

npm version License: MIT Node.js React TypeScript GitHub stars GitHub issues

Server-Driven UI Next.js Zod Turborepo

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)

End-to-end example

① server JSON ② renderer ③ interactive UI

① Server JSON — nested 3-level tree (ContainerCardCounter):

{
  "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} />,
      }}
    />
  )
}

Table of Contents


At a glance

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

Installation

pnpm add @lodado/sdui-template zod@^4.3.6
# or
npm install @lodado/sdui-template zod@^4.3.6

With default UI components and design tokens:

pnpm add @lodado/sdui-template-component @lodado/sdui-design-files

This repo targets Zod v4. Use a compatible version in your app.


Quick start

'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.


Packages

Package npm Role
@lodado/sdui-template npm SDUI renderer, store, hooks, schema, normalization
@lodado/sdui-template-component npm Radix UI component map (sduiComponents)
@lodado/sdui-design-files npm Design tokens and CSS variables (Atlassian DS)
@lodado/sdui-document npm Headless block document domain, patches, permissions
@lodado/sdui-document-react npm Notion-like block editor (React + ProseMirror)
@lodado/sdui-mcp npm 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

MCP & AI assistants

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.

Prerequisites

  • Node.js 24+ (same as CI)
  • An MCP-capable client: Cursor, Claude Code, Claude Desktop, Windsurf, Cline, etc.

Connect MCP — Cursor

Option A — UI (recommended)

  1. Open Cursor SettingsMCP (or Features → MCP)
  2. Click Add MCP Server
  3. Name: sdui
  4. Command: npx
  5. Args: -y, @lodado/sdui-mcp
  6. 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.

Connect MCP — Claude Code

One command:

claude mcp add sdui -- npx -y @lodado/sdui-mcp

Or commit .mcp.json at the repo root (same JSON shape as Cursor above) so the team shares one config.

Connect MCP — Claude Desktop

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.

Verify the connection

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:

  1. Confirm Node 24+ is on your PATH in the IDE's environment
  2. Run manually: npx -y @lodado/sdui-mcp — should start without errors (Ctrl+C to stop)
  3. Reload MCP in your client after config changes

MCP tools reference

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

Workflow A — call MCP tools directly

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

Workflow B — snapshot into your repo (teams)

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.md

Run /sdui-sync in Claude Code. It writes .ai/sdui/ (syntax, components, examples + manifest) and re-syncs only changed files when older than 7 days.

AI guide for all packages (including block documents)

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 storybookapps/docs (port 6006)

Storybook paths for block documents:

  • Document/AdaptertoSduiLayoutDocument + renderer
  • Document/Catalog — every block type
  • DocumentEditor — interactive editor stories

Philosophy

1. The server sends screen intent, not JSX

SDUI documents are data — component names, state, children, and references. No arbitrary code over the wire.

2. The client renders only registered components

type maps to an explicit components registry. The server drives layout; the client controls rendering authority.

3. State is owned per node

Field Purpose
state Component data and behavior
attributes Style, className, HTML-like props
children Nested UI
reference Other node IDs to read/subscribe

4. Subscriptions beat full-tree re-renders

SduiLayoutStore + SubscriptionManager propagate node-level changes instead of re-rendering the entire tree.

5. Headless core, optional component layer

@lodado/sdui-template is the rendering engine. Bring your own design system, or use @lodado/sdui-template-component for a fast start.


Architecture

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

Rendering flow

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

Component resolution priority

  1. componentOverrides.byNodeId[node.id]
  2. componentOverrides.byNodeType[node.type]
  3. components[node.type]
  4. defaultComponentFactory

Document model

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:

  • id must be unique within the document
  • type must match a registered component key
  • Put component data in state, presentation props in attributes
  • Nest UI with children; link nodes with reference

Node references

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.


Component package

'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.


Development

pnpm workspace + Turborepo:

pnpm install
pnpm dev          # parallel dev across packages
pnpm storybook    # Storybook on port 6006
pnpm build
pnpm test
pnpm typecheck
pnpm lint

Per-package:

pnpm --filter @lodado/sdui-template test
pnpm --filter @lodado/sdui-document build

When to use

  • 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

What this library does not do

  • Data fetching, auth, or persistence policies (app responsibility)
  • Final accessibility guarantees (component responsibility)
  • Arbitrary HTML or server-delivered function execution

License

MIT

About

Server-Driven UI Template Library for React. A flexible and powerful template system for building server-driven user interfaces with dynamic layouts and components.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages