---
title: Code generation
description: One Framewerk screen and the React it lowers to — routes, components, state and actions mapped field by field.
status: shipped
url: https://getairframe.com/framewerk/guides/code-generation
---

> Part of the Framewerk docs. Index of every page: /framewerk/llms.txt

# Code generation

A Framewerk document carries enough structure to generate code from: `route`
for the URL, `instructions` for what the path cannot say, `components` for
reusable subtrees, and action strings for navigation. This guide shows one
screen and the React it lowers to, then the mapping table.

Nothing here runs a generator for you — it is the contract a generator (or an
agent) follows.

## One screen

**A sign-in screen**

```json
{
  "formatVersion": "1.0",
  "screens": [
    {
      "id": "signin",
      "route": "/signin",
      "title": "Sign in",
      "instructions": ["Stack screen; on success replace with home"],
      "stateVars": { "email": { "type": "string", "default": "" } },
      "layout": {
        "_id": "root",
        "type": "column",
        "gap": 16,
        "padding": 24,
        "children": [
          { "_id": "h", "type": "text", "variant": "heading", "content": "Sign in" },
          { "_id": "e", "type": "input", "label": "Email", "placeholder": "you@example.com" },
          { "_id": "go", "type": "button", "variant": "primary", "content": "Continue", "action": "navigate(home)" }
        ]
      }
    },
    {
      "id": "home",
      "route": "/",
      "title": "Home",
      "layout": {
        "_id": "hr",
        "type": "column",
        "padding": 24,
        "children": [{ "_id": "hh", "type": "text", "variant": "heading", "content": "Home" }]
      }
    }
  ],
  "flows": [{ "from": "signin", "to": "home", "trigger": "Continue" }]
}
```

## The React it lowers to

```tsx
// app/signin/page.tsx  — route "/signin" from screen.signin
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";

export default function SigninPage() {
  const router = useRouter();
  const [email, setEmail] = useState(""); // stateVars.email

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16, padding: 24 }}>
      <h1>Sign in</h1> {/* text[variant=heading] */}
      <label>
        Email
        <input
          placeholder="you@example.com"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </label>
      <button onClick={() => router.push("/")}>Continue</button>
      {/* action: navigate(home) → route "/" from screen.home */}
    </div>
  );
}
```

`route` picks the file. `instructions` become comments or JSDoc above the
component. `stateVars` become `useState` seeds (always strings in the spec;
coerce in the component). Action strings become the router call.

## Mapping table

| Framewerk | React (Next App Router) |
| --- | --- |
| `screens[].id` | Route segment / component name |
| `screens[].route` | File path under `app/` (`/signin` → `app/signin/page.tsx`) |
| `screens[].instructions` | File-level comment or JSDoc |
| `stateVars` | `useState` per variable (string initial) |
| `type: "column"` / `"row"` | `flexDirection: "column"` / `"row"` on a `div` |
| `type: "text"` | `<p>` or `<span>`; `variant: "heading"` → `<h1>`–`<h3>` |
| `type: "button"` | `<button>` |
| `type: "input"` | `<input>` (+ `<label>` when `label` is set) |
| `type: "image"` | `<img>` |
| `action: "navigate(id)"` | `router.push(screen[id].route)` |
| `action: "back()"` | `router.back()` |
| `action: "setState(k, v)"` / `toggleState(k)` | `setState` / updater on the matching `useState` |
| `action: "scrollTo(anchor)"` | `scrollIntoView()` on the element the `anchor` names |
| `action: "openUrl(address)"` | `<a href target="_blank" rel="noopener noreferrer">`, or `window.open` on a control that is not a link |
| `components{}` | A React component per key; `props` → typed props |
| `slots` | Children / render-prop slots on the component |
| `styles[].tokens` | CSS custom properties on a root wrapper |
| `showWhen` / `hideWhen` | Conditional render (`{cond && <…/>}`) |
| `flows[]` | Documentation only — navigation comes from `action` |

React Native maps the same way: `column`/`row` are already flex defaults,
`button` → `Pressable`, `navigate` → the router your stack uses.

## Stability

The fields above are what a generator may rely on: `route`, `instructions`,
`stateVars`, the six actions, `components`/`props`/`slots`, and `showWhen`.
Anything else in a document is layout detail — nice to lower, not required.
