Code Export
Convert specs to copy-paste React/JSX code
The specToJSX() function converts any Stream UI spec into a self-contained React component file. This lets users go from AI-generated UI to real, editable code in one step.
Basic Usage
import { specToJSX } from "@prototyperai/stream-ui"
const spec = {
root: "card",
elements: {
card: { type: "Card", props: {}, children: ["heading", "btn"] },
heading: { type: "Heading", props: { text: "Hello World", level: 2 } },
btn: { type: "Button", props: { label: "Click me" } },
},
}
const code = specToJSX(spec)Output:
"use client"
import { Card } from "@/registry/ui/card"
import { Button } from "@/registry/ui/button"
export function GeneratedUI() {
return (
<Card>
<h2>
Hello World
</h2>
<Button>
Click me
</Button>
</Card>
)
}Options
Pass a CodegenOptions object to customize the output:
const code = specToJSX(spec, {
indent: 4,
componentName: "LoginForm",
importPrefix: "@/components/ui",
})Prop
Type
How Components Map to Imports
Each component type maps to an import file. For example, Button imports from {prefix}/button, CardTitle imports from {prefix}/card, and SelectItem imports from {prefix}/select. Multiple components from the same file are grouped into a single import statement.
Two component types map to HTML elements instead of imports:
Headingrenders as<h1>through<h6>based on thelevelpropTextrenders as<p>
Dynamic Value Conversion
State Expressions
$state expressions convert to variable references using camelCase naming derived from the JSON Pointer path:
{ "text": { "$state": "/user/name" } }{userName}Two-Way Bindings
$bindState expressions generate value + onChange pairs appropriate to the component type:
| Component | Generated Code |
|---|---|
| Input, Textarea | value={email} onChange={(e) => setEmail(e.target.value)} |
| Checkbox, Switch | checked={agreed} onCheckedChange={setAgreed} |
| Select, RadioGroup | value={role} onValueChange={setRole} |
| Slider | value={[volume]} onValueChange={([v]) => setVolume(v)} |
Template Expressions
$template strings convert to template literals:
{ "content": { "$template": "Hello ${/name}!" } }{`Hello ${name}!`}Computed and Conditional Expressions
$computed and $cond expressions generate placeholder comments since their logic cannot be statically resolved:
{/* computed: formatDate */}
{/* conditional value */}Item and Index Expressions
Inside repeated elements, $item and $index expressions reference the .map() callback parameters:
{item.title}
{index}State Management
When the spec includes a state object, specToJSX generates useState hooks for each top-level state key:
{ "state": { "count": 0, "items": [], "name": "World" } }import { useState } from "react"
export function GeneratedUI() {
const [count, setCount] = useState(0)
const [items, setItems] = useState([])
const [name, setName] = useState("World")
// ...
}Actions
Event bindings in the on field convert to inline handlers:
| Action | Generated Code |
|---|---|
setState | setCount(5) or setName(otherVar) |
toggleState | setActive((prev) => !prev) |
appendItem | setItems((prev) => [...prev, newItem]) |
removeItem | setItems((prev) => prev.filter((_, i) => i !== index)) |
| Custom actions | Comment with action name and params |
The press event maps to onClick in React.
Repeat (Lists)
Elements with a repeat binding generate .map() calls:
{
"type": "Card",
"repeat": { "source": "/todos", "itemKey": "id" },
"children": ["todoItem"]
}{todos.map((item) => (
<Card key={item.id}>
{/* children */}
</Card>
))}Visibility (Conditional Rendering)
Elements with visible conditions generate conditional expressions:
{
"type": "Text",
"props": { "content": "Loading..." },
"visible": { "$state": "/isLoading" }
}{isLoading && (
<p>
Loading...
</p>
)}Complex conditions use the appropriate operators:
{ "visible": { "$state": "/count", "gt": 0 } }{count > 0 && (
// ...
)}Limitations
The code export is designed to produce a working starting point, not a perfect final result. Some dynamic behavior cannot be statically converted:
$computedexpressions become comments since the function implementations live outside the spec$condexpressions become placeholder comments- Custom actions (not
setState/toggleState/appendItem/removeItem) generate comment stubs - Cross-field validation is not included in the exported code
- State is flat — nested state paths are converted to flat
useStatecalls at the top level
The intended workflow is: AI generates a spec, the user previews it live with the Renderer, and then exports to code for further customization.
Use Case
AI Model --> Streaming Spec --> Live Preview (Renderer)
|
[Export]
|
JSX Component --> Customize & ShipThe exported code uses your project's actual component imports, so it integrates directly into your codebase without any Stream UI runtime dependency.