$npx -y skills add vercel-labs/json-render --skill solidSolidJS renderer for json-render. Use when building @json-render/solid catalogs/registries, wiring Renderer providers, implementing bindings/actions, or troubleshooting Solid-specific reactivity patterns.
| 1 | # @json-render/solid |
| 2 | |
| 3 | `@json-render/solid` renders json-render specs into Solid component trees with fine-grained reactivity. |
| 4 | |
| 5 | ## Quick Start |
| 6 | |
| 7 | ```tsx |
| 8 | import { Renderer, JSONUIProvider } from "@json-render/solid"; |
| 9 | import type { Spec } from "@json-render/solid"; |
| 10 | import { registry } from "./registry"; |
| 11 | |
| 12 | export function App(props: { spec: Spec | null }) { |
| 13 | return ( |
| 14 | <JSONUIProvider registry={registry} initialState={{}}> |
| 15 | <Renderer spec={props.spec} registry={registry} /> |
| 16 | </JSONUIProvider> |
| 17 | ); |
| 18 | } |
| 19 | ``` |
| 20 | |
| 21 | ## Create a Catalog |
| 22 | |
| 23 | ```typescript |
| 24 | import { defineCatalog } from "@json-render/core"; |
| 25 | import { schema } from "@json-render/solid/schema"; |
| 26 | import { z } from "zod"; |
| 27 | |
| 28 | export const catalog = defineCatalog(schema, { |
| 29 | components: { |
| 30 | Button: { |
| 31 | props: z.object({ |
| 32 | label: z.string(), |
| 33 | variant: z.enum(["primary", "secondary"]).nullable(), |
| 34 | }), |
| 35 | description: "Clickable button", |
| 36 | }, |
| 37 | Card: { |
| 38 | props: z.object({ title: z.string() }), |
| 39 | description: "Card container", |
| 40 | }, |
| 41 | }, |
| 42 | actions: { |
| 43 | submit: { description: "Submit data" }, |
| 44 | }, |
| 45 | }); |
| 46 | ``` |
| 47 | |
| 48 | ## Define Components |
| 49 | |
| 50 | Components receive `ComponentRenderProps` from the renderer: |
| 51 | |
| 52 | ```ts |
| 53 | interface ComponentRenderProps<P = Record<string, unknown>> { |
| 54 | element: UIElement<string, P>; |
| 55 | children?: JSX.Element; |
| 56 | emit: (event: string) => void; |
| 57 | on: (event: string) => EventHandle; |
| 58 | bindings?: Record<string, string>; |
| 59 | loading?: boolean; |
| 60 | } |
| 61 | ``` |
| 62 | |
| 63 | Example: |
| 64 | |
| 65 | ```tsx |
| 66 | import type { BaseComponentProps } from "@json-render/solid"; |
| 67 | |
| 68 | export function Button(props: BaseComponentProps<{ label: string }>) { |
| 69 | return ( |
| 70 | <button onClick={() => props.emit("press")}>{props.props.label}</button> |
| 71 | ); |
| 72 | } |
| 73 | ``` |
| 74 | |
| 75 | ## Create a Registry |
| 76 | |
| 77 | ```typescript |
| 78 | import { defineRegistry } from "@json-render/solid"; |
| 79 | import { catalog } from "./catalog"; |
| 80 | import { Card } from "./Card"; |
| 81 | import { Button } from "./Button"; |
| 82 | |
| 83 | const { registry, handlers, executeAction } = defineRegistry(catalog, { |
| 84 | components: { |
| 85 | Card, |
| 86 | Button, |
| 87 | }, |
| 88 | actions: { |
| 89 | submit: async (params, setState, state) => { |
| 90 | // custom action logic |
| 91 | }, |
| 92 | }, |
| 93 | }); |
| 94 | ``` |
| 95 | |
| 96 | ## Spec Structure |
| 97 | |
| 98 | ```json |
| 99 | { |
| 100 | "root": "card1", |
| 101 | "elements": { |
| 102 | "card1": { |
| 103 | "type": "Card", |
| 104 | "props": { "title": "Hello" }, |
| 105 | "children": ["btn1"] |
| 106 | }, |
| 107 | "btn1": { |
| 108 | "type": "Button", |
| 109 | "props": { "label": "Click me" }, |
| 110 | "on": { |
| 111 | "press": { "action": "submit" } |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | ``` |
| 117 | |
| 118 | ## Providers |
| 119 | |
| 120 | - `StateProvider`: state model read/write and controlled mode via `store` |
| 121 | - `VisibilityProvider`: evaluates `visible` conditions |
| 122 | - `ValidationProvider`: field validation + `validateForm` integration |
| 123 | - `ActionProvider`: runs built-in and custom actions |
| 124 | - `JSONUIProvider`: combined provider wrapper |
| 125 | |
| 126 | ## Hooks |
| 127 | |
| 128 | - `useStateStore`, `useStateValue`, `useStateBinding` |
| 129 | - `useVisibility`, `useIsVisible` |
| 130 | - `useActions`, `useAction` |
| 131 | - `useValidation`, `useOptionalValidation`, `useFieldValidation` |
| 132 | - `useBoundProp` |
| 133 | - `useUIStream`, `useChatUI` |
| 134 | |
| 135 | ## Built-in Actions |
| 136 | |
| 137 | Handled automatically by `ActionProvider`: |
| 138 | |
| 139 | - `setState` |
| 140 | - `pushState` |
| 141 | - `removeState` |
| 142 | - `validateForm` |
| 143 | |
| 144 | ## Dynamic Props and Bindings |
| 145 | |
| 146 | Supported expression forms include: |
| 147 | |
| 148 | - `{"$state": "/path"}` |
| 149 | - `{"$bindState": "/path"}` |
| 150 | - `{"$bindItem": "field"}` |
| 151 | - `{"$template": "Hi ${/user/name}"}` |
| 152 | - `{"$computed": "fn", "args": {...}}` |
| 153 | - `{"$cond": <condition>, "$then": <value>, "$else": <value>}` |
| 154 | |
| 155 | Use `useBoundProp` in components for writable bound values: |
| 156 | |
| 157 | ```tsx |
| 158 | import { useBoundProp } from "@json-render/solid"; |
| 159 | |
| 160 | function Input(props: BaseComponentProps<{ value?: string }>) { |
| 161 | const [value, setValue] = useBoundProp( |
| 162 | props.props.value, |
| 163 | props.bindings?.value, |
| 164 | ); |
| 165 | return ( |
| 166 | <input |
| 167 | value={String(value() ?? "")} |
| 168 | onInput={(e) => setValue(e.currentTarget.value)} |
| 169 | /> |
| 170 | ); |
| 171 | } |
| 172 | ``` |
| 173 | |
| 174 | `useStateValue`, `useStateBinding`, and the `state` / `errors` / `isValid` fields from `useFieldValidation` are reactive accessors in Solid. Call them as functions inside JSX, `createMemo`, or `createEffect`. |
| 175 | |
| 176 | ## Solid Reactivity Rules |
| 177 | |
| 178 | - Do not destructure component props in function signatures when values need to stay reactive. |
| 179 | - Keep changing reads inside JSX expressions, `createMemo`, or `createEffect`. |
| 180 | - Context values are exposed through getter-based objects so consumers always observe live signals. |
| 181 | |
| 182 | ## Streaming UI |
| 183 | |
| 184 | ```tsx |
| 185 | import { useUIStream, Renderer } from "@json-render/solid"; |
| 186 | |
| 187 | const stream = useUIStream({ api: "/api/generate-ui" }); |
| 188 | await stream.send("Create a support dashboard"); |
| 189 | |
| 190 | <Renderer |
| 191 | spec={stream.spec} |
| 192 | registry={registry} |
| 193 | loading={stream.isStreaming} |
| 194 | />; |
| 195 | ``` |
| 196 | |
| 197 | Use `useChatUI` for chat + UI generation flows. |