solid by vercel-labs/json-render
npx skills add https://github.com/vercel-labs/json-render --skill solid@json-render/solid 将 json-render 规范渲染成具有细粒度响应式的 Solid 组件树。
import { Renderer, JSONUIProvider } from "@json-render/solid";
import type { Spec } from "@json-render/solid";
import { registry } from "./registry";
export function App(props: { spec: Spec | null }) {
return (
<JSONUIProvider registry={registry} initialState={{}}>
<Renderer spec={props.spec} registry={registry} />
</JSONUIProvider>
);
}
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/solid/schema";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
Button: {
props: z.object({
label: z.string(),
variant: z.enum(["primary", "secondary"]).nullable(),
}),
description: "可点击的按钮",
},
Card: {
props: z.object({ title: z.string() }),
description: "卡片容器",
},
},
actions: {
submit: { description: "提交数据" },
},
});
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
组件从渲染器接收 ComponentRenderProps:
interface ComponentRenderProps<P = Record<string, unknown>> {
element: UIElement<string, P>;
children?: JSX.Element;
emit: (event: string) => void;
on: (event: string) => EventHandle;
bindings?: Record<string, string>;
loading?: boolean;
}
示例:
import type { BaseComponentProps } from "@json-render/solid";
export function Button(props: BaseComponentProps<{ label: string }>) {
return (
<button onClick={() => props.emit("press")}>{props.props.label}</button>
);
}
import { defineRegistry } from "@json-render/solid";
import { catalog } from "./catalog";
import { Card } from "./Card";
import { Button } from "./Button";
const { registry, handlers, executeAction } = defineRegistry(catalog, {
components: {
Card,
Button,
},
actions: {
submit: async (params, setState, state) => {
// 自定义操作逻辑
},
},
});
{
"root": "card1",
"elements": {
"card1": {
"type": "Card",
"props": { "title": "Hello" },
"children": ["btn1"]
},
"btn1": {
"type": "Button",
"props": { "label": "Click me" },
"on": {
"press": { "action": "submit" }
}
}
}
}
StateProvider:通过 store 进行状态模型的读写和控制模式VisibilityProvider:评估 visible 条件ValidationProvider:字段验证 + validateForm 集成ActionProvider:运行内置和自定义操作JSONUIProvider:组合的提供者包装器useStateStore, useStateValue, useStateBindinguseVisibility, useIsVisibleuseActions, useActionuseValidation, useOptionalValidation, useFieldValidationuseBoundPropuseUIStream, useChatUI由 ActionProvider 自动处理:
setStatepushStateremoveStatevalidateForm支持的表达式形式包括:
{"$state": "/path"}{"$bindState": "/path"}{"$bindItem": "field"}{"$template": "Hi ${/user/name}"}{"$computed": "fn", "args": {...}}{"$cond": <condition>, "$then": <value>, "$else": <value>}在组件中使用 useBoundProp 来处理可写的绑定值:
import { useBoundProp } from "@json-render/solid";
function Input(props: BaseComponentProps<{ value?: string }>) {
const [value, setValue] = useBoundProp(
props.props.value,
props.bindings?.value,
);
return (
<input
value={String(value() ?? "")}
onInput={(e) => setValue(e.currentTarget.value)}
/>
);
}
useStateValue、useStateBinding 以及 useFieldValidation 中的 state / errors / isValid 字段在 Solid 中都是响应式访问器。在 JSX、createMemo 或 createEffect 内部将它们作为函数调用。
createMemo 或 createEffect 内部。import { useUIStream, Renderer } from "@json-render/solid";
const stream = useUIStream({ api: "/api/generate-ui" });
await stream.send("创建一个支持仪表板");
<Renderer
spec={stream.spec}
registry={registry}
loading={stream.isStreaming}
/>;
使用 useChatUI 来处理聊天 + UI 生成流程。
每周安装量
118
仓库
GitHub 星标数
13.3K
首次出现
14 天前
安全审计
安装于
codex116
cursor115
gemini-cli114
kimi-cli114
opencode114
github-copilot114
@json-render/solid renders json-render specs into Solid component trees with fine-grained reactivity.
import { Renderer, JSONUIProvider } from "@json-render/solid";
import type { Spec } from "@json-render/solid";
import { registry } from "./registry";
export function App(props: { spec: Spec | null }) {
return (
<JSONUIProvider registry={registry} initialState={{}}>
<Renderer spec={props.spec} registry={registry} />
</JSONUIProvider>
);
}
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/solid/schema";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
Button: {
props: z.object({
label: z.string(),
variant: z.enum(["primary", "secondary"]).nullable(),
}),
description: "Clickable button",
},
Card: {
props: z.object({ title: z.string() }),
description: "Card container",
},
},
actions: {
submit: { description: "Submit data" },
},
});
Components receive ComponentRenderProps from the renderer:
interface ComponentRenderProps<P = Record<string, unknown>> {
element: UIElement<string, P>;
children?: JSX.Element;
emit: (event: string) => void;
on: (event: string) => EventHandle;
bindings?: Record<string, string>;
loading?: boolean;
}
Example:
import type { BaseComponentProps } from "@json-render/solid";
export function Button(props: BaseComponentProps<{ label: string }>) {
return (
<button onClick={() => props.emit("press")}>{props.props.label}</button>
);
}
import { defineRegistry } from "@json-render/solid";
import { catalog } from "./catalog";
import { Card } from "./Card";
import { Button } from "./Button";
const { registry, handlers, executeAction } = defineRegistry(catalog, {
components: {
Card,
Button,
},
actions: {
submit: async (params, setState, state) => {
// custom action logic
},
},
});
{
"root": "card1",
"elements": {
"card1": {
"type": "Card",
"props": { "title": "Hello" },
"children": ["btn1"]
},
"btn1": {
"type": "Button",
"props": { "label": "Click me" },
"on": {
"press": { "action": "submit" }
}
}
}
}
StateProvider: state model read/write and controlled mode via storeVisibilityProvider: evaluates visible conditionsValidationProvider: field validation + validateForm integrationActionProvider: runs built-in and custom actionsJSONUIProvider: combined provider wrapperuseStateStore, useStateValue, useStateBindinguseVisibility, useIsVisibleuseActions, useActionuseValidation, useOptionalValidation, useFieldValidationuseBoundPropHandled automatically by ActionProvider:
setStatepushStateremoveStatevalidateFormSupported expression forms include:
{"$state": "/path"}{"$bindState": "/path"}{"$bindItem": "field"}{"$template": "Hi ${/user/name}"}{"$computed": "fn", "args": {...}}{"$cond": <condition>, "$then": <value>, "$else": <value>}Use useBoundProp in components for writable bound values:
import { useBoundProp } from "@json-render/solid";
function Input(props: BaseComponentProps<{ value?: string }>) {
const [value, setValue] = useBoundProp(
props.props.value,
props.bindings?.value,
);
return (
<input
value={String(value() ?? "")}
onInput={(e) => setValue(e.currentTarget.value)}
/>
);
}
useStateValue, useStateBinding, and the state / errors / isValid fields from useFieldValidation are reactive accessors in Solid. Call them as functions inside JSX, createMemo, or createEffect.
createMemo, or createEffect.import { useUIStream, Renderer } from "@json-render/solid";
const stream = useUIStream({ api: "/api/generate-ui" });
await stream.send("Create a support dashboard");
<Renderer
spec={stream.spec}
registry={registry}
loading={stream.isStreaming}
/>;
Use useChatUI for chat + UI generation flows.
Weekly Installs
118
Repository
GitHub Stars
13.3K
First Seen
14 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex116
cursor115
gemini-cli114
kimi-cli114
opencode114
github-copilot114
Genkit JS 开发指南:AI 应用构建、错误排查与最佳实践
7,000 周安装
useUIStream, useChatUI