JSON Render UI
Overview
Transform natural language UI requests into working dashboards and applications. Claude acts as the translation layer (prompt → JSON), the skill provides the rendering runtime (JSON → UI).
Architecture:
User prompt → Claude (constrained by catalog) → UITree JSON → Preact renderer → UI
Output Format
Claude emits a UITree structure:
{
"root": "main",
"elements": {
"main": {
"key": "main",
"type": "Grid",
"props": { "columns": 2, "gap": "md" },
"children": ["metric1", "metric2"]
},
"metric1": {
"key": "metric1",
"type": "Metric",
"props": {
"label": "Revenue",
"valuePath": "/revenue",
"format": "currency"
}
},
"metric2": {
"key": "metric2",
"type": "Metric",
"props": {
"label": "Growth",
"valuePath": "/growth",
"format": "percent"
}
}
},
"data": {
"revenue": 125000,
"growth": 0.15
}
}
Component Catalog
Claude can ONLY use these components. This is the guardrail.
Layout Components
| Component |
Props |
Children |
Description |
Card |
title?, description?, padding?: sm|md|lg |
Yes |
Container with optional header |
Grid |
columns?: 1-4, gap?: sm|md|lg |
Yes |
CSS grid layout |
Stack |
direction?: horizontal|vertical, gap?: sm|md|lg, align?: start|center|end|stretch |
Yes |
Flexbox stack |
Data Display
| Component |
Props |
Children |
Description |
Metric |
label, valuePath, format?: number|currency|percent, trend?: up|down|neutral, trendValue? |
No |
Single KPI display |
Chart |
type: bar|line|pie|area, dataPath, title?, height? |
No |
Data visualization |
Table |
dataPath, columns: [{key, label, format?: text|currency|date|badge}] |
No |
Tabular data |
List |
dataPath, emptyMessage? |
Yes |
Rendered list from array |
Interactive
| Component |
Props |
Children |
Description |
Button |
label, action, variant?: primary|secondary|danger|ghost, size?: sm|md|lg, disabled? |
No |
Clickable action |
Select |
label?, bindPath, options: [{value, label}], placeholder? |
No |
Dropdown select |
DatePicker |
label?, bindPath, placeholder? |
No |
Date input |
Typography
| Component |
Props |
Children |
Description |
Heading |
text, level?: h1|h2|h3|h4 |
No |
Section heading |
Text |
content, variant?: body|caption|label, color?: default|muted|success|warning|danger |
No |
Text paragraph |
Status
| Component |
Props |
Children |
Description |
Badge |
text, variant?: default|success|warning|danger|info |
No |
Status indicator |
Alert |
type: info|success|warning|error, title, message?, dismissible? |
No |
Notification banner |
Special
| Component |
Props |
Children |
Description |
Divider |
label? |
No |
Visual separator |
Empty |
title, description?, action?, actionLabel? |
No |
Empty state |
Data Binding
Props ending in Path (e.g., valuePath, dataPath, bindPath) reference the data model using JSON Pointer syntax:
/revenue → data.revenue
/users/0/name → data.users[0].name
/filters/dateRange → data.filters.dateRange
Visibility Conditions
Any element can have a visible property:
{
"type": "Alert",
"props": { "type": "error", "title": "Error" },
"visible": { "path": "/hasError" }
}
Visibility expressions:
true / false — Static
{ "path": "/some/path" } — Truthy check
{ "and": [...] } — All conditions true
{ "or": [...] } — Any condition true
{ "not": {...} } — Negation
{ "eq": [a, b] } — Equality
Generating Output
Step 1: Emit UITree JSON
Analyze the user's request and emit a UITree structure. Include sample data that matches the schema.
Step 2: Create Artifact
Create an HTML artifact that:
- Embeds the UITree JSON
- Renders the UI
Start from assets/template.html, which already carries the embedded renderer —
there is no separate renderer.js to inline.
Constraints
DO:
- Use only catalog components
- Provide realistic sample data
- Use semantic component choices (Metric for KPIs, Table for tabular data)
- Keep trees flat (no deep nesting beyond 3 levels)
DO NOT:
- Invent new component types
- Use arbitrary HTML/CSS
- Omit required props
- Create deeply nested structures
Example
User: "Create a sales dashboard with revenue, orders, and a chart showing monthly trends"
Claude outputs:
{
"root": "dashboard",
"elements": {
"dashboard": {
"key": "dashboard",
"type": "Stack",
"props": { "direction": "vertical", "gap": "lg" },
"children": ["header", "metrics", "chartCard"]
},
"header": {
"key": "header",
"type": "Heading",
"props": { "text": "Sales Dashboard", "level": "h1" }
},
"metrics": {
"key": "metrics",
"type": "Grid",
"props": { "columns": 3, "gap": "md" },
"children": ["revenue", "orders", "avgOrder"]
},
"revenue": {
"key": "revenue",
"type": "Card",
"props": { "padding": "md" },
"children": ["revenueMetric"]
},
"revenueMetric": {
"key": "revenueMetric",
"type": "Metric",
"props": {
"label": "Total Revenue",
"valuePath": "/revenue",
"format": "currency",
"trend": "up",
"trendValue": "12%"
}
},
"orders": {
"key": "orders",
"type": "Card",
"props": { "padding": "md" },
"children": ["ordersMetric"]
},
"ordersMetric": {
"key": "ordersMetric",
"type": "Metric",
"props": {
"label": "Orders",
"valuePath": "/orders",
"format": "number"
}
},
"avgOrder": {
"key": "avgOrder",
"type": "Card",
"props": { "padding": "md" },
"children": ["avgMetric"]
},
"avgMetric": {
"key": "avgMetric",
"type": "Metric",
"props": {
"label": "Avg Order Value",
"valuePath": "/avgOrderValue",
"format": "currency"
}
},
"chartCard": {
"key": "chartCard",
"type": "Card",
"props": { "title": "Monthly Trends", "padding": "md" },
"children": ["chart"]
},
"chart": {
"key": "chart",
"type": "Chart",
"props": {
"type": "line",
"dataPath": "/monthlyData",
"height": 300
}
}
},
"data": {
"revenue": 284500,
"orders": 1247,
"avgOrderValue": 228,
"monthlyData": [
{ "month": "Jan", "value": 18000 },
{ "month": "Feb", "value": 22000 },
{ "month": "Mar", "value": 28000 },
{ "month": "Apr", "value": 24000 },
{ "month": "May", "value": 32000 }
]
}
}
Then wrap in the artifact template from assets/template.html.
Files
assets/template.html — Complete artifact template with embedded renderer
assets/catalog.json — Machine-readable component schemas
references/uitree-format.md — Detailed UITree specification
1---2name: json-render-ui3description: Generate guardrailed UI from natural language. Emits constrained JSON that a Preact runtime renders. Use when the request is for a dashboard with metrics, charts, or tables; an admin panel; a data visualization interface; or a form-based application.4---56# JSON Render UI78## Overview910Transform natural language UI requests into working dashboards and applications. Claude acts as the translation layer (prompt → JSON), the skill provides the rendering runtime (JSON → UI).1112**Architecture:**13```14User prompt → Claude (constrained by catalog) → UITree JSON → Preact renderer → UI15```1617## Output Format1819Claude emits a **UITree** structure:2021```json22{23 "root": "main",24 "elements": {25 "main": {26 "key": "main",27 "type": "Grid",28 "props": { "columns": 2, "gap": "md" },29 "children": ["metric1", "metric2"]30 },31 "metric1": {32 "key": "metric1",33 "type": "Metric",34 "props": {35 "label": "Revenue",36 "valuePath": "/revenue",37 "format": "currency"38 }39 },40 "metric2": {41 "key": "metric2",42 "type": "Metric",43 "props": {44 "label": "Growth",45 "valuePath": "/growth",46 "format": "percent"47 }48 }49 },50 "data": {51 "revenue": 125000,52 "growth": 0.1553 }54}55```5657## Component Catalog5859Claude can ONLY use these components. This is the guardrail.6061### Layout Components6263| Component | Props | Children | Description |64|-----------|-------|----------|-------------|65| `Card` | `title?`, `description?`, `padding?: sm\|md\|lg` | Yes | Container with optional header |66| `Grid` | `columns?: 1-4`, `gap?: sm\|md\|lg` | Yes | CSS grid layout |67| `Stack` | `direction?: horizontal\|vertical`, `gap?: sm\|md\|lg`, `align?: start\|center\|end\|stretch` | Yes | Flexbox stack |6869### Data Display7071| Component | Props | Children | Description |72|-----------|-------|----------|-------------|73| `Metric` | `label`, `valuePath`, `format?: number\|currency\|percent`, `trend?: up\|down\|neutral`, `trendValue?` | No | Single KPI display |74| `Chart` | `type: bar\|line\|pie\|area`, `dataPath`, `title?`, `height?` | No | Data visualization |75| `Table` | `dataPath`, `columns: [{key, label, format?: text\|currency\|date\|badge}]` | No | Tabular data |76| `List` | `dataPath`, `emptyMessage?` | Yes | Rendered list from array |7778### Interactive7980| Component | Props | Children | Description |81|-----------|-------|----------|-------------|82| `Button` | `label`, `action`, `variant?: primary\|secondary\|danger\|ghost`, `size?: sm\|md\|lg`, `disabled?` | No | Clickable action |83| `Select` | `label?`, `bindPath`, `options: [{value, label}]`, `placeholder?` | No | Dropdown select |84| `DatePicker` | `label?`, `bindPath`, `placeholder?` | No | Date input |8586### Typography8788| Component | Props | Children | Description |89|-----------|-------|----------|-------------|90| `Heading` | `text`, `level?: h1\|h2\|h3\|h4` | No | Section heading |91| `Text` | `content`, `variant?: body\|caption\|label`, `color?: default\|muted\|success\|warning\|danger` | No | Text paragraph |9293### Status9495| Component | Props | Children | Description |96|-----------|-------|----------|-------------|97| `Badge` | `text`, `variant?: default\|success\|warning\|danger\|info` | No | Status indicator |98| `Alert` | `type: info\|success\|warning\|error`, `title`, `message?`, `dismissible?` | No | Notification banner |99100### Special101102| Component | Props | Children | Description |103|-----------|-------|----------|-------------|104| `Divider` | `label?` | No | Visual separator |105| `Empty` | `title`, `description?`, `action?`, `actionLabel?` | No | Empty state |106107## Data Binding108109Props ending in `Path` (e.g., `valuePath`, `dataPath`, `bindPath`) reference the data model using JSON Pointer syntax:110111- `/revenue` → `data.revenue`112- `/users/0/name` → `data.users[0].name`113- `/filters/dateRange` → `data.filters.dateRange`114115## Visibility Conditions116117Any element can have a `visible` property:118119```json120{121 "type": "Alert",122 "props": { "type": "error", "title": "Error" },123 "visible": { "path": "/hasError" }124}125```126127Visibility expressions:128- `true` / `false` — Static129- `{ "path": "/some/path" }` — Truthy check130- `{ "and": [...] }` — All conditions true131- `{ "or": [...] }` — Any condition true132- `{ "not": {...} }` — Negation133- `{ "eq": [a, b] }` — Equality134135## Generating Output136137### Step 1: Emit UITree JSON138139Analyze the user's request and emit a UITree structure. Include sample data that matches the schema.140141### Step 2: Create Artifact142143Create an HTML artifact that:1441. Embeds the UITree JSON1452. Renders the UI146147Start from `assets/template.html`, which already carries the embedded renderer —148there is no separate `renderer.js` to inline.149150## Constraints151152**DO:**153- Use only catalog components154- Provide realistic sample data155- Use semantic component choices (Metric for KPIs, Table for tabular data)156- Keep trees flat (no deep nesting beyond 3 levels)157158**DO NOT:**159- Invent new component types160- Use arbitrary HTML/CSS161- Omit required props162- Create deeply nested structures163164## Example165166**User:** "Create a sales dashboard with revenue, orders, and a chart showing monthly trends"167168**Claude outputs:**169170```json171{172 "root": "dashboard",173 "elements": {174 "dashboard": {175 "key": "dashboard",176 "type": "Stack",177 "props": { "direction": "vertical", "gap": "lg" },178 "children": ["header", "metrics", "chartCard"]179 },180 "header": {181 "key": "header",182 "type": "Heading",183 "props": { "text": "Sales Dashboard", "level": "h1" }184 },185 "metrics": {186 "key": "metrics",187 "type": "Grid",188 "props": { "columns": 3, "gap": "md" },189 "children": ["revenue", "orders", "avgOrder"]190 },191 "revenue": {192 "key": "revenue",193 "type": "Card",194 "props": { "padding": "md" },195 "children": ["revenueMetric"]196 },197 "revenueMetric": {198 "key": "revenueMetric",199 "type": "Metric",200 "props": {201 "label": "Total Revenue",202 "valuePath": "/revenue",203 "format": "currency",204 "trend": "up",205 "trendValue": "12%"206 }207 },208 "orders": {209 "key": "orders",210 "type": "Card",211 "props": { "padding": "md" },212 "children": ["ordersMetric"]213 },214 "ordersMetric": {215 "key": "ordersMetric",216 "type": "Metric",217 "props": {218 "label": "Orders",219 "valuePath": "/orders",220 "format": "number"221 }222 },223 "avgOrder": {224 "key": "avgOrder",225 "type": "Card",226 "props": { "padding": "md" },227 "children": ["avgMetric"]228 },229 "avgMetric": {230 "key": "avgMetric",231 "type": "Metric",232 "props": {233 "label": "Avg Order Value",234 "valuePath": "/avgOrderValue",235 "format": "currency"236 }237 },238 "chartCard": {239 "key": "chartCard",240 "type": "Card",241 "props": { "title": "Monthly Trends", "padding": "md" },242 "children": ["chart"]243 },244 "chart": {245 "key": "chart",246 "type": "Chart",247 "props": {248 "type": "line",249 "dataPath": "/monthlyData",250 "height": 300251 }252 }253 },254 "data": {255 "revenue": 284500,256 "orders": 1247,257 "avgOrderValue": 228,258 "monthlyData": [259 { "month": "Jan", "value": 18000 },260 { "month": "Feb", "value": 22000 },261 { "month": "Mar", "value": 28000 },262 { "month": "Apr", "value": 24000 },263 { "month": "May", "value": 32000 }264 ]265 }266}267```268269Then wrap in the artifact template from `assets/template.html`.270271## Files272273- `assets/template.html` — Complete artifact template with embedded renderer274- `assets/catalog.json` — Machine-readable component schemas275- `references/uitree-format.md` — Detailed UITree specification