Labsco
vercel-labs logo

solid

15,600

by vercel · part of vercel-labs/json-render

SolidJS renderer for json-render. Use when building @json-render/solid catalogs/registries, wiring Renderer providers, implementing bindings/actions, or…

🔥🔥🔥✓ VerifiedFreeQuick setup
🧰 Not standalone. This skill ships with vercel-labs/json-render and only works together with that tool — install the tool first, then add this skill.

This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.

by vercel

SolidJS renderer for json-render. Use when building @json-render/solid catalogs/registries, wiring Renderer providers, implementing bindings/actions, or… npx skills add https://github.com/vercel-labs/json-render --skill solid Download ZIPGitHub15.6k

@json-render/solid

@json-render/solid renders json-render specs into Solid component trees with fine-grained reactivity.

Create a Catalog

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:

interface ComponentRenderProps > {
 element: UIElement ;
 children?: JSX.Element;
 emit: (event: string) => void;
 on: (event: string) => EventHandle;
 bindings?: Record ;
 loading?: boolean;
}

Example:

import type { BaseComponentProps } from "@json-render/solid";

export function Button(props: BaseComponentProps ) {
 return (
 props.emit("press")}>{props.props.label} 
 );
}

Create a Registry

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

{
 "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

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:

import { useBoundProp } from "@json-render/solid";

function Input(props: BaseComponentProps ) {
 const [value, setValue] = useBoundProp(
 props.props.value,
 props.bindings?.value,
 );
 return (
 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.

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

import { useUIStream, Renderer } from "@json-render/solid";

const stream = useUIStream({ api: "/api/generate-ui" });
await stream.send("Create a support dashboard");

 ;

Use useChatUI for chat + UI generation flows.