solid — Claude Code skill: render SolidJS UI from JSON specs
Skill to render JSON-specs into reactive SolidJS components. Use it when building UI catalogs, wiring providers, or debugging Solid reactivity patterns.
npx skills add vercel-labs/json-render --skill solid -a claude-codeWhat solid does
@json-render/solid transforms JSON descriptions into live Solid components with fine-grained reactivity. Wrap your app in JSONUIProvider, pass a spec and registry, and the skill handles state bindings, element visibility, form validation, and actions.
Four providers do the heavy lifting: StateProvider syncs state, VisibilityProvider evaluates conditions, ValidationProvider checks fields, ActionProvider runs built-in and custom logic. Components receive emit for events and useBoundProp for two-way bindings. Specs support templates, computed values, conditionals, and repeats via repeat.statePath.
The skill enforces Solid reactivity rules: do not destructure props in function signatures if they change; keep reactive reads inside JSX, createMemo, or createEffect. For streaming UI generation, use useUIStream or useChatUI.
What it helps with
- render UI from JSON specs into a SolidJS app
- bind component state to JSON bindings
- validate forms and sync state across the tree
- handle component events and run custom actions
- stream generated UI from an API
Example prompts for the agent
- Build a catalog with `defineCatalog`, register components in `defineRegistry`, wrap it in `JSONUIProvider`
- Write a component, use `useBoundProp` to bind input values to state
- Set up `useUIStream` and send a prompt; the API generates a spec, `Renderer` displays it
How to install solid
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill solid -a claude-code2Manually: put the files in place
~/.claude/skills/solid/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/solid/
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill solid -a codex2Manually: put the files in place
.agents/skills/solid/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill solid -a cursor2Manually: put the files in place
.cursor/skills/solid/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install vercel-labs/json-render solid2Manually: put the files in place
.github/skills/solid/SKILL.md1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill solid -a gemini-cli2Platform CLI
gemini skills install https://github.com/vercel-labs/json-render --path skills/solidManually: put the files in place
.devin/skills/solid/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill solid -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set20
The whole set →Similar
Questions about solid
How do I install solid in Claude Code?
Run npx skills add vercel-labs/json-render --skill solid -a claude-code, then check ~/.claude/skills/solid/SKILL.md.
What does solid do?
The skill renders JSON-described UIs into reactive SolidJS components, automatically syncing state, visibility, validation, and event handling.
How do I make a two-way binding for form inputs?
Use useBoundProp(props.props.value, props.bindings?.value) — it returns the current value and a write function. Read the value inside JSX to keep it reactive.
Source text
SKILL.md · Apache-2.0
---
name: solid
description: SolidJS renderer for json-render. Use when building @json-render/solid catalogs/registries, wiring Renderer providers, implementing bindings/actions, or troubleshooting Solid-specific reactivity patterns.
---
# @json-render/solid
`@json-render/solid` renders json-render specs into Solid component trees with fine-grained reactivity.
## Quick Start
```tsx
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>
);
}
```
## Create a Catalog
```typescript
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" },
},
});
```
## Define Components
Components receive `ComponentRenderProps` from the renderer:
```ts
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:
```tsx
import type { BaseComponentProps } from "@json-render/solid";
export function Button(props: BaseComponentProps<{ label: string }>) {
return (
<button onClick={() => props.emit("press")}>{props.props.label}</button>
);
}
```
## Create a Registry
```typescript
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
},
},
});
```
## Spec Structure
```json
{
"root": "card1",
"elements": {
"card1": {
"type": "Card",
"props": { "title": "Hello" },
"children": ["btn1"]
},
"btn1": {
"type": "Button",
"props": { "label": "Click me" },
"on": {
"press": { "action": "submit" }
}
}
}
}
```
## Providers
- `StateProvider`: state model read/write and controlled mode via `store`
- `VisibilityProvider`: evaluates `visible` conditions
- `ValidationProvider`: field validation + `validateForm` integration
- `ActionProvider`: runs built-in and custom actions
- `JSONUIProvider`: combined provider wrapper
## Hooks
- `useStateStore`, `useStateValue`, `useStateBinding`
- `useVisibility`, `useIsVisible`
- `useActions`, `useAction`
- `useValidation`, `useOptionalValidation`, `useFieldValidation`
- `useBoundProp`
- `useUIStream`, `useChatUI`
## Built-in Actions
Handled automatically by `ActionProvider`:
- `setState`
- `pushState`
- `removeState`
- `validateForm`
## Dynamic Props and Bindings
Nested lists can set `repeat.statePath` to `{ "$item": "field" }` inside an enclosing repeat.
Supported 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:
```tsx
import { useBoundProp, type BaseComponentProps } from "@json-render/solid";
function Input(props: BaseComponentProps<{ value?: string }>) {
const [, setValue] = useBoundProp(props.props.value, props.bindings?.value);
return (
<input
value={String(props.props.value ?? "")}
onInput={(e) => setValue(e.currentTarget.value)}
/>
);
}
```
`useBoundProp` returns a scalar value and a write-back setter. Read the resolved prop inside JSX so later state updates remain reactive.
`useStateValue`, `useStateBinding`, and the `state` / `errors` / `isValid` fields from `useFieldValidation` are reactive accessors in Solid. Call them as functions inside JSX, `createMemo`, or `createEffect`.
## Solid Reactivity Rules
- Do not destructure component props in function signatures when values need to stay reactive.
- Keep changing reads inside JSX expressions, `createMemo`, or `createEffect`.
- Context values are exposed through getter-based objects so consumers always observe live signals.
## Streaming UI
```tsx
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.
© vercel-labs · Apache-2.0 · vercel-labs/json-render