Velloo documentation
Markdown for LLMs ↗From design to code
emit_code returns an agent-consumed IR, not paste-ready JSX; your agent reads it alongside your app's conventions and writes the real file.
Velloo’s designs are made of your real components, so the last mile is short. But it’s still a mile: your app has routing, an import alias, a formatter, maybe a data layer. Velloo doesn’t guess at any of that.
The philosophy: IR, not paste
emit_code returns a structured intermediate representation: JSX-shaped, but deliberately not a finished file. No imports, no function wrapper, no formatting pass. Your agent reads the IR alongside your app’s existing code and conventions, then writes the real file in your style: the right import paths, the right page wrapper, your prettier config, your lint rules.
This is a feature, not a limitation. Paste-ready output has to guess at your conventions and gets them wrong; an IR plus an agent that has read your codebase gets them right.
{ "tool": "emit_code", "args": { "screenId": "pricing" } }
What the IR contains
The result is pure data; nothing was written to disk. A shadcn screen looks like this:
{
"screen": { "id": "pricing", "name": "Pricing" },
"jsx": "<div className=\"flex flex-col gap-16 bg-background px-6 py-24\">…</div>",
"componentsUsed": ["Badge", "Box", "Button", "Card", "Heading", "Text"],
"iconsUsed": ["Check", "Sparkles"],
"snippetsUsed": [
{
"id": "pricing-tier",
"componentName": "PricingTier",
"params": [
{ "name": "title", "type": "string" },
{ "name": "highlighted", "type": "boolean", "default": "false" }
],
"jsx": "<Card className=\"…\">…</Card>",
"componentsToInstall": ["card", "badge"],
"helpersToMaterialize": [],
"warnings": []
}
],
"classesUsed": ["bg-background", "text-muted-foreground", "gap-16"],
"componentsToInstall": ["badge", "button", "card"],
"helpersToMaterialize": [],
"warnings": []
}
jsxis the screen body, using the library’s own identifiers and the framework’s native styling idiom: Tailwind classes verbatim for a shadcn folder,sxobjects importing from@mui/materialfor a MUI folder. Tailwind classes come pre-consolidated: no duplicates, deterministic merge order.componentsUsed/iconsUsed: what to import. Library ids map to your components alias; icons import fromlucide-react.snippetsUsedlists every snippet the screen references, each with its own IR: a PascalCasecomponentName, typedparams, and a JSX body. The agent decides whether to materialize each as a real component file or inline the subtree.classesUsed: the unique Tailwind classes in the output, for verifying your app’s Tailwind setup covers everything.componentsToInstall: shadcn primitives the screen uses, as kebab names ready fornpx shadcn@latest add <names>. Compare against your app before installing;list_componentsreportsinstalledInAppper component.helpersToMaterializelists the Velloo composition helpers that carry runtime logic and need authoring in your app:Gradient,SVG,Image,Layer,Divider.Box,Stack,Container,Heading,Text,Prose, andPlaceholderlower to plain HTML elements, andIconto alucide-reactimport, so they never appear here.warnings: non-fatal caveats where something couldn’t be expressed faithfully in JSX and needs agent attention.tailwindV3Compatis present only when the host app is on Tailwind v3: v4 classes from the design (screen and snippet bodies) to rename or rework while writing the file, such as v4shadow-sm⇒ v3shadow.diagnosticsis present when the full-screen check finds something: invalid Tailwind utilities, undefined CSS variables, v3 incompatibilities, raw colors that won’t theme-flip, or a component that threw while rendering, each with a node path. Fix them on the canvas before writing the file.
On a MUI, Chakra, or Ant Design folder, or a no-framework folder with inline styles, componentsToInstall and helpersToMaterialize are always empty (there is nothing to shadcn add) and tailwindV3Compat never appears.
Two things never emit: board layout (frame positions, sizes, groups are canvas-only data) and interactivity (designs are static by construction; handlers are yours to write).
Snippets as components
emit_snippet is the same idea scoped to one snippet, useful when you want a design-system piece as its own file without emitting a whole screen:
{ "tool": "emit_snippet", "args": { "snippetId": "pricing-tier" } }
Returns the snippet’s id, PascalCase componentName, typed params (optional params emit as name?), the JSX body, and its own componentsToInstall, helpersToMaterialize, and warnings, plus tailwindV3Compat and diagnostics when they apply. Snippets emit with a typed className?: string prop, so instances can keep their per-instance class overrides.
The import alias
The IR mentions an import prefix for snippet identifiers, @/components/ui by default. Set it once in the design folder’s config (codegen.componentsAlias in .design/config.json, see Config) or override per call:
{ "tool": "emit_code", "args": { "screenId": "pricing", "componentsAlias": "~/components/ui" } }
It’s a hint. The agent picks the actual import path from your app’s conventions; that’s the whole point of the IR model.
CLI equivalents
The same paths exist without an agent in the loop:
velloo emit pricing # print header comments + the JSX body to stdout
velloo emit pricing --to pricing-ir.json # write the full IR as JSON
velloo render pricing --to pricing.png # headless render — .html or .png
velloo emit takes a screen id or a path to a screen JSON file; with neither, it picks one interactively. --design selects the design, and --components-alias mirrors the tool argument. The stdout header lists the screen, components, lucide icons, snippets, and any Tailwind v3 renames; --to writes the IR as JSON, including tailwindV3Compat when it applies. The CLI does not run the diagnostics pass; use emit_code for that. velloo render renders a screen outside the canvas entirely: --to picks .html (no browser needed) or .png (headless Chromium), and --w/--h set the viewport (default 1440×900).
End to end
A realistic pass, with your agent driving:
- Design the screen on the canvas (the compose-and-verify loop) until the compare screenshot looks right in both modes.
- The agent calls
emit_codefor the screen (andemit_snippetfor any snippet it wants as its own file). - It reads the IR next to your app: route structure, import alias, existing components. It runs
npx shadcn@latest addfor whatevercomponentsToInstallnames that the app is missing, and applies anytailwindV3Compatrenames. - It writes
app/pricing/page.tsx: real imports, your page conventions, snippets materialized as components where that earns its keep. - It runs your app’s own lint and format commands over the new file. Velloo has no prettier pass on purpose; your formatter is the source of truth.
The design stays in the folder. When the page changes, edit the design and emit again: the screen is the artifact you iterate, the file is a projection of it.