Quick Start
Render a Framewerk screen in one HTML file, then change it, embed it in React, and have an agent generate one.
Quick Start
Four steps: render a spec → change it → install in a React app → have an agent generate one. Each step is one code block.
1. Render a screen — one HTML file, no build
Save as index.html and open it over HTTP (not file:// — ES modules need
an origin). Source: the no-build example, pointed at the published packages.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Framewerk — no build step</title>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.3.1",
"react/jsx-runtime": "https://esm.sh/react@18.3.1/jsx-runtime",
"react-dom/client": "https://esm.sh/react-dom@18.3.1/client?external=react",
"lucide-react": "https://esm.sh/lucide-react@0.574.0?external=react",
"@framewerk/spec": "https://esm.sh/@framewerk/spec",
"@framewerk/spec/fixtures": "https://esm.sh/@framewerk/spec/fixtures",
"@framewerk/renderer": "https://esm.sh/@framewerk/renderer?external=react"
}
}
</script>
</head>
<body>
<div id="root" style="width:393px;min-height:500px;border:1px solid #ddd"></div>
<script type="module">
import { createElement as h, useState } from "react";
import { createRoot } from "react-dom/client";
import { RendererProvider, NodeRenderer, useRenderer } from "@framewerk/renderer";
import { sampleSpec } from "@framewerk/spec/fixtures";
function Screen({ onNav }) {
const { state } = useRenderer();
const screen = state.spec.screens?.find((s) => s.id === state.currentScreen);
if (!screen?.layout) return null;
return h(NodeRenderer, {
node: screen.layout,
comps: state.spec.components ?? {},
path: [],
stateValues: state.previewStateValues,
onNav,
});
}
// The provider holds no navigation state — the host owns screenId.
function App() {
const [screenId, setScreenId] = useState("menu");
return h(
RendererProvider,
{ spec: sampleSpec, screenId, fontStrategy: "none" },
h(Screen, { onNav: setScreenId }),
);
}
createRoot(document.getElementById("root")).render(h(App));
</script>
</body>
</html>
Serve it (npx serve .) and you get a working two-screen prototype: tap
Order to navigate, Toggle oat milk to flip a state variable. The
?external=react on the CDN URLs is load-bearing — without it you get two
Reacts on one page.
2. Change it — state, an action, a navigate
A spec is plain JSON. Add a screen with a state variable and wire buttons to actions (source: the format reference):
{
"formatVersion": "1.0",
"screens": [
{
"id": "menu",
"stateVars": { "oatMilk": { "type": "boolean", "default": "false" } },
"layout": {
"_id": "menu-root",
"type": "column",
"gap": 16,
"padding": 24,
"children": [
{ "_id": "menu-title", "type": "text", "variant": "heading", "content": "Coffee Cart" },
{ "_id": "menu-oat", "type": "button", "content": "Toggle oat milk", "action": "toggleState(oatMilk)" },
{ "_id": "menu-badge", "type": "badge", "content": "Oat milk", "showWhen": "$oatMilk == true" },
{ "_id": "menu-order", "type": "button", "variant": "primary", "content": "Order", "action": "navigate(order)" }
]
}
}
]
}
toggleState and setState work out of the box. navigate() and back()
are reported through callbacks — your host owns the screen id, exactly as in
step 1.
3. Install in a React app
npm install @framewerk/renderer @framewerk/spec react react-dom
import { useState } from "react";
import { RendererProvider, NodeRenderer, useRenderer } from "@framewerk/renderer";
import { sampleSpec } from "@framewerk/spec/fixtures";
function Screen({ onNav }: { onNav: (id: string) => void }) {
const { state } = useRenderer();
const screen = state.spec.screens?.find((s) => s.id === state.currentScreen);
if (!screen?.layout) return null;
return (
<NodeRenderer
node={screen.layout}
comps={state.spec.components ?? {}}
path={[]}
stateValues={state.previewStateValues}
onNav={onNav}
/>
);
}
export default function App() {
const [screenId, setScreenId] = useState("menu");
return (
<RendererProvider spec={sampleSpec} screenId={screenId} fontStrategy="none">
<Screen onNav={setScreenId} />
</RendererProvider>
);
}
Source: the embed example. Swap sampleSpec for your own Spec when you
have one.
4. Have an agent generate one
Point a coding agent at the format reference and ask for a screen:
Write a Framewerk spec with two screens:
home(a heading, a search input, and a primary "Search" button) andresults(a list of three rows). The button navigates toresults. Validate it withvalidate()from@framewerk/spec.
Most agents that can read the format reference emit a valid document; the validator catches the ones that don't. For a maintained agent setup, see Skills and Agents & MCP.
Next: Core concepts · Node reference · Playground