The first problem I hit with JSON-driven UI
This library renders a React UI from a JSON layout sent by the server. It is useful for dashboards, dynamic forms, and CMS pages whose structure needs to change without a client deployment.
I initially rebuilt the recursive renderer and state management for each project. I extracted the shared pieces into a package so I would not have to solve the same problem repeatedly.
JSON document → normalized store → React renderer → node-level updates

A Storybook inspector showing the same layout as Document, Nodes, and Store.
Split state by node
The document is normalized into an entity map keyed by ID. A component subscribes only to its own node, and updateNodeState(id) notifies subscribers of that ID. Each component validates incoming server data with its own Zod schema.
When one Toggle changes
"use client";
import {
useSduiLayoutAction,
useSduiNodeSubscription,
} from "@lodado/sdui-template";
import { z } from "zod";
const toggleSchema = z.object({ checked: z.boolean() });
function Toggle({ id }: { id: string }) {
const { state } = useSduiNodeSubscription({
nodeId: id,
schema: toggleSchema,
});
const store = useSduiLayoutAction();
return (
<button
onClick={() =>
store.updateNodeState(id, { checked: !state.checked })
}
>
{state.checked ? "ON" : "OFF"}
</button>
);
}
Clicking the button re-renders the component subscribed to this Toggle, not the whole document.
Design philosophy
The server owns structure; the client owns interaction
The server decides which components appear and in what order. React components handle clicks and input. When JSON starts imitating every React behavior, the layout becomes harder to change rather than easier.
Think in changed nodes, not whole documents
The JSON tree is normalized into a nodes map and a rootId. Node lookup ends with an ID, and change notifications go only to subscribers of that ID. This keeps the update scope predictable even for large documents.
Do not trust server JSON
TypeScript cannot validate runtime input. Each custom component owns a Zod schema for its state, catching invalid JSON at the component boundary instead of failing deep in the render tree.
Make relationships and exceptions explicit
When one node needs another node's state, it declares a reference. When the default component is not enough, consumers can override it by node ID or type. I avoided hiding these cases in global state or renderer patches.
Not every screen needs SDUI
Plain React is simpler when the server has no reason to control the layout. SDUI is reserved for real server-driven requirements such as configurable dashboards, dynamic forms, and CMS pages.
The package is published on npm and is used by other projects in the monorepo.