Build with Veneer
Register the capabilities your app exposes, drop in a provider, a canvas and a hook, and let your users redesign the UI with AI — safely sandboxed.
Introduction
Veneer is an open-source React npm package — an “MCP for the UI layer.” Instead of shipping toggles for every layout, you expose a typed surface of data sources and actions; your users describe how they want a page to look, and the model writes real component code. That code runs inside a hard security sandbox and can only ever use what you registered.
Installation
Install the package and add its peer dependencies (react ≥ 18, react-dom ≥ 18).
$ npm install @veneer/react @veneer/server@veneer/react is MIT-licensed; @veneer/server requires a Veneer license key (or use Veneer Cloud as the endpoint).Two packages, nothing else: the client has no runtime dependencies besides React 18 or 19. Both ship compiled ESM and TypeScript declarations, so Next.js, Vite and Remix need no extra configuration.
Quick start
Declare a registry, pass your real implementations, render the provider and the canvas, and wire any chat box to useVeneerGenerate(). The registry’s plain-English descriptions are handed to the model; its ids are what the bridge enforces at runtime.
const registry = { dataSources: [{ id: 'tasks', description: "The user's tasks." }], actions: [ { id: 'createTask', description: 'Create a task.' }, { id: 'updateTask', description: 'Update a task.' }, // no deleteTask → generated UIs can never delete ], }; <VeneerProvider registry={registry} actions={actions} data={{ tasks }}> <VeneerCanvas minHeight={620} /> <YourChatBox /> // useVeneerGenerate() </VeneerProvider>
The generator endpoint (POST /api/veneer) is createVeneerHandler from @veneer/server: self-hosted with your LLM and a license key, or pointed at Veneer Cloud with an API key. Either way it returns { code, intent, reply, token }; code is null for chat turns, and the client applies code only with a valid token.
How it works
- 1You set the rulesList the data and actions your app allows, and plug in the real functions. That list is the only thing the AI can use.
- 2Users describe itThey open the chat and say how they want the page to look. The model writes a self-contained component.
- 3It runs safelyThe component runs in a sandboxed interpreter that can only describe what to draw; your page redraws it through an allowlist, and its only channel out is a validated bridge to your registered actions.
The registry
The registry is the contract. A generated UI can only ever invoke an action whose id appears in registry.actions, and read a source whose id appears in registry.dataSources. If there is no deleteTask, code that calls it is refused before it runs and the bridge rejects it at runtime. Declare args with types — the bridge validates them — and add returns to the actions whose return value generated code may see.
Treat each registered action like a public endpoint the user can call with arbitrary arguments — always validate and authorize on the server side inside your implementation.
The veneer API (inside the interpreter)
Generated code is plain JSX with these globals available (no imports):
veneer.data(sourceId)Read a registered data source (returns the current value).veneer.action(id, args)Trigger a registered action (returns a Promise).render(<App />)Mount the component.
fetch, window, document, cookies, storage and external resources do not exist inside the interpreter — all I/O goes through the bridge. The server endpoint (createVeneerHandler from @veneer/server) owns the registry, caps and rate-limits requests, and scans generated code before returning it.
Components & API
VeneerProviderVeneerCanvasuseVeneerGenerate()useVeneer()Branding
Pass a theme to VeneerProvider and the generated UI takes your brand (your own chat UI can read the same tokens from useVeneer()): colours (per light/dark mode), radii, fonts and shadows become --veneer-* variables on the canvas, and Tailwind utilities such as bg-primary, text-foreground, border-border and rounded-md resolve to them. The model is told to use exactly that vocabulary.
Generated UI is real DOM in your page, so your fonts, focus order and overlays apply as they are; Tailwind is compiled per view and scoped to the canvas, so neither side can restyle the other. The canvas sizes itself to its content and flows with the page. Pass mode: 'system' to follow the OS, or 'light' / 'dark' to follow your own toggle — this page does the latter.
Security model
Three independent layers, none of which depend on the model behaving, plus a static scan and a watchdog:
- An interpreter, not a browserGenerated code runs in QuickJS (compiled to WebAssembly) inside a Web Worker. window, document, fetch, location, cookies and storage do not exist there; only JSON crosses the boundary, and every entry has a CPU deadline and a memory cap.
- Allowlisted renderingThe interpreter only describes what to draw. Your page redraws it with its own React through an allowlist: no links, images, scripts, iframes, url() styles, fixed positioning or ids that collide with yours. Tailwind is compiled for exactly the classes used and scoped to the canvas.
- Validated bridgeEvery action is validated — registered id, declared args and types, rate limit — before the real implementation runs; only declared return values and registered data cross back.
- Scan + watchdogGenerated code is parsed and refused if it reaches for window, document, location, network globals, this, imports or unregistered ids. If the interpreter hangs or stops answering heartbeats, the worker is terminated, the canvas is reset and onSecurityViolation fires.
- Host requirementsServe @veneer/react's dist/veneer-worker.js from your origin (default path /veneer-worker.js). If your page sets a Content-Security-Policy, allow 'wasm-unsafe-eval' in script-src and 'self' in worker-src; nothing else changes.
The sandbox is a capability boundary, not a replacement for server-side authorization. Validate arguments and enforce permissions inside every action implementation.
Ready to try it on your own app?