# Configuration URL: https://docs.stageboard.design/configuration Look up every field of stageboard.config.ts, its type, its default and what it changes. stageboard reads its settings from `stageboard.config.ts` in your project. `npx stageboard init` creates one. Wrap the object in `defineConfig` from `stageboard` to get types. ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", agent: { provider: "claude" }, }) ``` ## Finding the config file Every command looks for `stageboard.config.ts`, `stageboard.config.mts`, `stageboard.config.js` or `stageboard.config.mjs`, starting in the current directory and walking up to the filesystem root. The first match wins, and its directory becomes the project root: every relative path in the config is resolved from there. Pass `--config ` to any command to use a specific file. The config is loaded with [jiti](https://github.com/unjs/jiti), so TypeScript works without a build step and `__dirname` is available. ## Options | Option | Type | Default | What it does | |---|---|---|---| | `stories` | `string[]` | required | Globs for story files, relative to the project root. `node_modules`, `.next`, `dist` and `.stageboard` are always skipped. | | `preview` | `string` | none | Path to a module whose exports apply to every story: `decorators` (wrappers such as providers) and `parameters`. Import global CSS here. | | `variantsFile` | `string` | `"stageboard.variants.ts"` | Path of the generated variants registry. stageboard rewrites it whenever `*.variant.tsx` files change; commit it. | | `variantGlobs` | `string[]` | `["**/*.variant.{tsx,jsx,ts,js}"]` | Globs for variant files. | | `ignore` | `string[]` | `[]` | Extra globs to skip when scanning for stories and variants, added to the built-in ones. | | `port` | `number` | `STAGEBOARD_PORT` or `6060` | Port for `stageboard dev`. If it is taken, the next free port is used. `--port` overrides it. | | `publicUrl` | `string` | `STAGEBOARD_PUBLIC_URL` | The URL the board is reached at from the browser, for example through a tunnel or a remote machine. Hot reload connects to this host, and `dev` prints it. | | `framework` | `"react"` | none | The UI framework. React is the only supported value, and leaving it out means React. | | `vite` | `UserConfig` or `() => UserConfig \| Promise` | none | Vite config merged into stageboard's own: plugins, aliases, `define`, CSS options. Use `nextPreset()` from `stageboard/next` for Next.js apps. The React plugin is added unless you pass one. | | `agent` | `AgentConfig` | `{ provider: "claude" }` | Which agent handles annotations and variant requests. See below. | | `instructions` | `string` | none | Project notes appended to every agent prompt under "Project notes", for example "Use the tokens in `src/theme.ts`". | stageboard keeps local state, caches and screenshots in `.stageboard/` next to the config. Add it to `.gitignore`. ## Agent `agent` picks the CLI that runs when someone sends an annotation. Each variant is a tagged object; the [Agents guide](/guides/agents) explains each one in detail. | `provider` | Fields | Defaults | |---|---|---| | `"claude"` | `background?: boolean`, `model?: string`, `permissionMode?: string`, `extraArgs?: string[]` | `background: true` (runs `claude --bg`; `false` runs `claude -p`), `permissionMode: "acceptEdits"` | | `"codex"` | `model?: string`, `sandbox?: "read-only" \| "workspace-write" \| "danger-full-access"`, `extraArgs?: string[]` | `sandbox: "workspace-write"` (runs `codex exec`) | | `"atmos"` | `project?`, `checkout?`, `device?`, `harness?`, `model?`, `permissionMode?`, `baseUrl?`, `org?` (all `string`), `auth?: "browser" \| "cli"`, `sameWorktree?: boolean` | `device: "current"`, `harness: "claude_local"`, `permissionMode: "acceptEdits"`, `auth: "browser"`, `sameWorktree: true` | | `"command"` | `command: string` (required), `detached?: boolean` | `detached: false`. Runs in a shell. The prompt is written to a file whose path is in `$STAGEBOARD_PROMPT_FILE` and replaces `{promptFile}` in the command; the job id is in `$STAGEBOARD_JOB_ID` and replaces `{jobId}`. With `detached: true`, exit code 0 leaves the job running until the agent reports completion. | Claude and Codex agents get the stageboard MCP server attached automatically. ## Complete example ```ts title="stageboard.config.ts" import tailwind from "@tailwindcss/vite" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx", "stageboard/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", variantsFile: "stageboard.variants.ts", variantGlobs: ["src/**/*.variant.tsx"], ignore: ["src/legacy/**"], port: 6060, vite: { plugins: [tailwind()] }, agent: { provider: "codex", sandbox: "workspace-write" }, instructions: "Use the design tokens in src/theme.ts. Never add new dependencies.", }) ``` For a Next.js app, replace `vite` with the preset described in [Next.js](/guides/nextjs). # Agents URL: https://docs.stageboard.design/guides/agents Choose which agent stageboard sends requests to, configure it, and see what every request contains. stageboard sends every request from Annotate, Variants, Reconcile and Flows to a coding agent. You pick the agent with the `agent` option in `stageboard.config.ts`; without it, stageboard uses `claude`. ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx"], agent: { provider: "codex", model: "gpt-5-codex" }, }) ``` `npx stageboard dev` prints the provider it uses on start. The agent always runs in the git root of the folder that holds `stageboard.config.ts` (or that folder, outside git), so it edits the checkout you are looking at. ## Providers | Provider | Runs | Shows up in | | --- | --- | --- | | `claude` | `claude --bg` (or `claude -p` with `background: false`) | `claude agents` | | `codex` | `codex exec --json` | Codex | | `atmos` | `atmos session new` | atmOS sessions | | `command` | any shell command | your tool | ### claude Starts a Claude Code background session named `stageboard: ` and polls `claude agents --json --all` until it is no longer busy. Cancelling a job runs `claude stop `. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `background` | `boolean` | `true` | `false` runs `claude -p --output-format stream-json --verbose` as a child process instead, and streams its messages into the job log. | | `model` | `string` | Claude's default | Passed as `--model`. | | `permissionMode` | `string` | `"acceptEdits"` | Passed as `--permission-mode`. | | `extraArgs` | `string[]` | `[]` | Appended to the `claude` arguments. | ### codex Runs `codex exec --json -C --sandbox ` and streams its events into the job log. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `model` | `string` | Codex's default | Passed as `-m`. | | `sandbox` | `"read-only" \| "workspace-write" \| "danger-full-access"` | `"workspace-write"` | Passed as `--sandbox`. | | `extraArgs` | `string[]` | `[]` | Appended to the `codex exec` arguments. | ### atmos Runs `atmos session new --json -p `, with a title made from the note, and polls `atmos session get --json` until the session is idle, done or failed. Cancelling runs `atmos session cancel `. Sessions start as the person who asked for them. The first time someone starts an agent, stageboard asks them to sign in to atmOS: it shows a code to enter in atmOS, the same device-code sign-in `atmos login` uses. The sign-in is kept in that person's browser, in an httpOnly cookie, and never on the machine. Each `atmos` command stageboard runs gets that person's token and none of the machine's own atmOS credentials. So on a shared machine every session belongs to whoever asked for it, and stageboard keeps working when an atmOS session that started it ends. While the page is open, stageboard renews the sign-in. If the page is closed for longer than the token lives, a running job still finishes when the agent reports with `complete_job`. | Option | Type | Default | What it does | | --- | --- | --- | --- | | `project` | `string` | none | Passed as `--project`. | | `checkout` | `string` | none | Passed as `--checkout`. | | `device` | `string` | `"current"` | Passed as `--device`. | | `harness` | `string` | `"claude_local"` | Passed as `--harness`. | | `model` | `string` | none | Passed as `--model`. | | `permissionMode` | `string` | `"acceptEdits"` | Passed as `--harness-permission-mode`. | | `auth` | `"browser"` \| `"cli"` | `"browser"` | `"browser"` signs each person in from stageboard. `"cli"` runs `atmos` as whatever the CLI on the machine is logged in as (`atmos login` or `ATMOS_API_KEY`). | | `baseUrl` | `string` | `ATMOS_BASE_URL`, else the atmos CLI's region | The atmOS API to sign in against. | | `org` | `string` | the login's org | The org to start sessions in, for a login that belongs to several. | | `sameWorktree` | `boolean` | `true` | Prepends a "Where to work" section that tells the session to work in the existing worktree and branch (via its `worktree_enter` tool), not a new one, because you are watching that checkout live. `false` leaves the prompt as is. | ### command Runs any shell command in the agent's working directory. stageboard writes the prompt to a temporary file first (`/stageboard-.md`). | Option | Type | Default | What it does | | --- | --- | --- | --- | | `command` | `string` | required | The shell command. `{promptFile}` is replaced with the prompt file path and `{jobId}` with the job id. | | `detached` | `boolean` | `false` | The command only launches the agent elsewhere and returns. Exit code 0 leaves the job running until the agent reports completion. | The command also gets two environment variables: `STAGEBOARD_PROMPT_FILE` (the prompt file) and `STAGEBOARD_JOB_ID`. Its output goes to the job log. By default the job is running until the agent reports completion or the command exits, whichever comes first; exit code 0 marks it done, any other code marks it failed. Set `detached: true` when the command starts a session in another tool (a tmux pane, a remote agent) and returns at once. The job then stays running until the agent reports with the `complete_job` MCP tool or `npx stageboard done [summary] [--failed]`, as the prompt tells it, or until you cancel it. A non-zero exit still marks the job failed, since the launcher itself failed. This one starts an interactive Claude Code session in a tmux session per job: ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx"], agent: { provider: "command", command: `tmux new-session -d -s sb-{jobId} 'claude "$(cat {promptFile})"'`, detached: true, }, }) ``` ## Project notes `instructions` is a string that stageboard appends to every prompt under `## Project notes`. Use it for things every request should know, such as where shared components live or which design tokens to use. ```ts title="stageboard.config.ts" export default defineConfig({ stories: ["src/**/*.stories.tsx"], instructions: "Use the tokens in src/styles/tokens.css. Never add new colors.", }) ``` ## What a request contains The prompt depends on the kind of request (edit, variants, adopt, new page, flow), but an annotation on a story includes: - The note and what to do with it, for example "Change the `Hero` component the user pointed at, directly in place". - The marked region: the story id, x, y, width and height in page pixels and the viewport size. - A cropped screenshot of exactly that area, with a small margin, as a file path. - The component chain under the selection, innermost first. - Your reference images, as file paths. - The variants in view, and whether to edit that variant's file or build new variants on it. - For sketches: every move, resize, text edit, block and freehand mark, plus before and after screenshots. - How to check the work with `npx stageboard shot` and the stageboard MCP tools. - Rules: work in the current tree, don't commit, push or start dev servers, follow the project's conventions. - The job id and how to report completion. - Your `instructions`, under `## Project notes`. The prompt ends with a `Page context (JSON):` block with the URL, page title, CSS selector, element text, the component chain with each component's `sourcePath` and `sourceLine`, the selection's tag and rect, the viewport, the variant boundary and its active key, and the story id. ```json { "url": "http://localhost:6060/__stageboard/frame.html?story=landing-hero--default", "title": "Acme", "selector": "section.hero > h1", "text": "Ship faster", "components": [ { "name": "HeroTitle", "sourcePath": "src/landing/hero.tsx", "sourceLine": 42 }, { "name": "Hero", "sourcePath": "src/landing/hero.tsx", "sourceLine": 12 } ], "selection": { "tag": "h1", "rect": { "x": 120, "y": 96, "width": 640, "height": 72 } }, "viewport": { "width": 1440, "height": 900 }, "variant": { "id": "landing.hero", "active": "original" }, "story": "landing-hero--default" } ``` stageboard also saves each prompt as `.stageboard/prompt-.md`, so you can read exactly what the agent got. ## Reporting completion Every prompt ends with the job id and asks the agent to report it finished as its last step, so the board stops showing the job as running: - the MCP tool `complete_job` with `job`, `summary` and optional `failed: true`, or - `npx stageboard done ""`, with `--failed` if it could not do what was asked. For `claude` and `codex`, stageboard attaches its MCP server to the agent automatically. See [MCP server](/reference/mcp). ## The skill `npx stageboard skill` installs the stageboard skill into `.claude/skills/stageboard` and `.agents/skills/stageboard` of the current folder (or the folder you pass). It teaches an agent how to write stories with mock data, page stories, flows and variants, and how to check its work. Pass `--claude` or `--codex` to install only one of them. Every prompt tells the agent to follow the skill when it is installed. ```bash npx stageboard skill ``` Related: [Annotate](/guides/annotate), [Variants](/guides/variants), [Flows](/guides/flows), [CLI](/reference/cli). # Annotate URL: https://docs.stageboard.design/guides/annotate Mark up any screen, component or flow, sketch the change you want on the live page, and send it to your agent with screenshots and source paths. Annotate is how you tell an agent what to change. You point at part of a screen, write a note, and stageboard sends the agent your note together with screenshots, the React components under that area and their source files. It works in Screens, Components, on a single screen and in the flow viewer. ## Comment on a region 1. Press `A`, or choose Annotate in the header. 2. Drag over the part of a screen, component, story or variant you mean. A click without dragging marks a small area around that point. 3. Choose Edit to change the code in place, or Variants (1 to 4) to get alternatives you can switch between. See [Variants](/guides/variants). 4. Write the note. Paste, drop or attach reference images. 5. Press `Enter` to send it (`Shift+Enter` adds a line). `Escape` closes the note. A pin appears on the area while the agent works. Click it to see the request, the agent's progress and its summary when it is done. Press `A` or `Escape` to stop annotating. ## Sketch on a single screen Open a screen on its own and press `A`. Besides Comment, the toolbar lets you change the live page directly, without touching code, and then send the result: | Tool | Key | What it does | | --- | --- | --- | | Comment | `C` | Drag over or click any part of the screen and write a note, as above. | | Select | `V` | Click an element to select it, drag to move it, drag a corner to resize it. Double-click text to retype it. `↑` selects the parent element. | | Draw | `P` | Freehand pen. | | Block | `B` | Drag a colored block where something should go. Double-click a block to label it. | | Components | `I` | A searchable palette of your app's component stories. Drag one onto the page, or click it to drop it in the middle. | | Undo | `Cmd+Z` or `Ctrl+Z` | Undo the last change. | | Clear | | Remove the whole sketch. | | Reconcile | `Enter` | Send the sketch to the agent. | `Backspace` or `Delete` removes the selected block, mark or component, or resets the moves and text edits of the selected element. `Escape` clears the selection, then leaves Annotate. Reconcile opens the same note as a comment, with the sketch attached. Choose Edit to make the page match the sketch, or Variants to get alternatives that take the sketch as their direction. The note is optional. ## What the agent receives Every request is written as a prompt for the agent you configured (see [Agents](/guides/agents)). It contains: - **Your note** and the request kind (Edit or Variants, and how many). - **The marked region** in page pixels and the viewport size, and a screenshot of that area with a small margin. - **The component chain** under the region, innermost first, each with its source file and line, in a `Page context (JSON):` block at the end of the prompt. - **Your reference images**, as file paths. - **Variants in view**, when you annotated a variant, so the agent edits or builds on that one. - **For a sketch:** every change as a line (moved or resized element with its selector and component, changed text before and after, blocks and their labels, components placed from a story id, freehand marks), plus a before screenshot and an after screenshot with the sketch applied. - **How to check and finish:** `npx stageboard shot` to look at its work, and the job id to report with the MCP tool `complete_job` or `npx stageboard done `. The agent is told to treat moves and resizes as layout intent, not pixel offsets, so the page should match the sketch at every width. ## Annotate a flow In the [flow viewer](/guides/flows#the-flow-viewer), annotations collect across the flow's screens instead of going out one by one: 1. Press `A`. Comment on one screen, or sketch on it with the same toolbar. 2. Press `→` to move to the next screen and keep going. Comments are marked with numbers on their screen. 3. Note on flow in the footer becomes Reconcile, with a count of your annotations. 4. Choose Reconcile. The dialog lists every comment and sketched screen in step order; remove any you don't want and add a note for all of them if you like. 5. Send. The whole batch goes to the agent as one request. The agent gets each annotation with its screen, story id and file: a screenshot of every commented area, and before and after screenshots of every sketched screen. It is told the annotations belong together, so the screens stay consistent. Variants asked for from a screen's variant outlines are sent as their own request, outside the batch. # Devtools in your app URL: https://docs.stageboard.design/guides/devtools Add the stageboard inspector to your running app so you can pick any element and send a change request from there. The stageboard devtools are a small inspector you mount in your own app during development. Click the Inspect bubble (or press Alt+Shift+C, or your `hotkey`), pick any element, and a panel shows its component chain with source paths, its variants, and a box to ask the agent for an edit or for variants. Requests go to the same agent bridge as the board, so `npx stageboard dev` must be running. ## Mount it `StageboardDevtools` from `stageboard/react` is a component that mounts the inspector and renders nothing itself. `mountDevtools` does the same from plain code and returns a function that unmounts it. Both mount once per page; later calls do nothing. Load it only in development, and import the generated `stageboard.variants.ts` next to it so variants you pick apply in the app too. ### Vite ```tsx title="src/main.tsx" import { createRoot } from "react-dom/client" import { App } from "./app" createRoot(document.getElementById("root")!).render() if (import.meta.env.DEV) { import("../stageboard.variants") import("stageboard/react").then(({ mountDevtools }) => mountDevtools()) } ``` ### Next.js ```tsx title="src/app/stageboard-dev.tsx" "use client" import "../../stageboard.variants" import { StageboardDevtools } from "stageboard/react" export default function StageboardDev() { return } ``` ```tsx title="src/app/layout.tsx" import dynamic from "next/dynamic" const StageboardDev = process.env.NODE_ENV === "development" ? dynamic(() => import("./stageboard-dev")) : () => null export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` Adjust the variants import path to where `variantsFile` points (by default `stageboard.variants.ts` next to the config). ## Options `StageboardDevtools` takes these props, and `mountDevtools` takes them as one object (`DevtoolsOptions`). | Option | Type | Default | What it does | |---|---|---|---| | `base` | `string` | `"/__stageboard"` | Where the stageboard server is reached from the page. The inspector calls `${base}/api/...`. | | `hotkey` | `string` | `"alt+shift+c"` | The shortcut that toggles picking, as modifiers and one key joined with `+`: `alt`, `shift`, `ctrl`, `meta` (`cmd`), and `mod`, which is Cmd on a Mac and Ctrl elsewhere. For example `"mod+shift+i"`. | | `embedded` | `boolean` | `true` when the URL has `?sb-embed` | Embedded mode, used inside the board's own frames: no bubble or panel, picks are sent to the parent window. Leave it unset in your app. | The options are read once, when the inspector mounts. ## Proxy `/__stageboard/*` to the stageboard server The inspector talks to the stageboard server with relative URLs, so your app's dev server has to forward `/__stageboard/*` to it. The stageboard server listens on port `6060` unless you changed `port`, and serves everything under `/__stageboard/`, so forward the path unchanged. ```ts title="vite.config.ts" import react from "@vitejs/plugin-react" import { defineConfig } from "vite" export default defineConfig({ plugins: [react()], server: { proxy: { "/__stageboard": `http://localhost:${process.env.STAGEBOARD_PORT ?? 6060}`, }, }, }) ``` ```ts title="next.config.ts" import type { NextConfig } from "next" const stageboard = `http://localhost:${process.env.STAGEBOARD_PORT ?? 6060}` const config: NextConfig = { async rewrites() { if (process.env.NODE_ENV !== "development") return [] return [{ source: "/__stageboard/:path*", destination: `${stageboard}/__stageboard/:path*` }] }, } export default config ``` The stageboard server does not send CORS headers, so pointing `base` at `http://localhost:6060/__stageboard` from another origin fails. Use the proxy. ## Using it - **Inspect** toggles picking. Hover shows the component name and size; click selects. Escape cancels picking or closes the panel. - The panel lists the component chain. Click a name to aim the request at that component instead of the innermost one. - If the element is inside a `variant(...)` boundary, the panel lists Original and every variant; click one to switch it live. - Write a note, choose **Edit** or **Variants** (1 to 4), and press Send or Cmd/Ctrl+Enter. Send is disabled while the bridge is offline. While the agent works, the affected element is outlined with a progress pill. See [Annotate](/guides/annotate) for the board's own annotation tools and [Variants](/guides/variants) for how variants are written. # Flows URL: https://docs.stageboard.design/guides/flows Build ordered journeys through your screens, describe and play each step, store them in stageboard/board.json and render a whole flow into one image. A flow is an ordered list of screens that a user goes through, such as signing up or changing a setting. Each step can say what is happening on the screen and what the user does on it, and the flow viewer plays those actions with a cursor. Flows are built from [page stories](/guides/screens). ## Create a flow Open Flows and choose one of these: - **New flow.** Give it a name and describe the journey: who starts where, what they do, where they end up. You can attach reference images. The agent picks your existing screens, builds the ones that are missing, and saves the flow. Missing screens show as an animated placeholder until their story exists. - **Suggest flows.** The agent reads your screens and routes and proposes the main journeys through the app, grouped by area. - **By hand.** Drag a screen from the Screens sidebar or a screen card onto a flow, drag steps to reorder them, and use the remove button on a step to take it out. The add tile at the end of a flow opens a picker: choose an existing screen, or describe a New screen and the agent builds it next to the screen before it. Double-click a flow's name to rename it. Note on flow sends a note about the whole flow to the agent, for example a missing step or wording that should match everywhere. Delete removes the flow but keeps its screens. ## The flow viewer Click a step to open the viewer. It shows one screen at the selected device size, with the step's note and its numbered actions below it. When the screen has loaded, its actions play with a cursor, a click ripple marks each click, and each action is marked done or failed as it runs. Play runs the whole flow: every screen plays its actions, then the viewer moves to the next one. | Key | What it does | | --- | --- | | `←` `→` | Previous or next screen. A horizontal trackpad swipe does the same. | | `Enter` | Use the screen: clicks and keys go to the page. Clicking the screen does the same. | | `Escape` | Hand control back to the viewer while you use the screen; otherwise close the viewer. | | `R` | Replay the screen from the start. | | `A` | Annotate. See [Annotate](/guides/annotate#annotate-a-flow). | The edit button on the step bar opens the note and the actions, one action per line. `Cmd+Enter` or `Ctrl+Enter` saves. ```text title="Actions, one per line" click Settings click Edit phone menu type Name: Main menu press Enter hover Help scroll Billing wait 800 ``` A target is an element's visible text, `aria-label`, placeholder or label, or a CSS selector. A line without a known verb is a click on that text. If no step in a flow has a note or actions yet, Describe steps asks the agent to write them. ## `stageboard/board.json` Flows are stored in `stageboard/board.json` next to your `stageboard.config.ts`, together with the screens agents are building for them (`dreams`). Commit it, so the whole team sees the same flows. ```json title="stageboard/board.json" { "dreams": [], "flows": [ { "id": "a1b2c3d4", "name": "Sign up", "group": "Onboarding", "description": "A visitor creates an account from the home page.", "steps": [ { "id": "s1", "storyId": "pages--home", "note": "The visitor lands on the marketing page.", "actions": [{ "do": "click", "target": "Get started" }] }, { "id": "s2", "storyId": "pages--login", "note": "They enter their email to get a sign-in link.", "actions": [{ "do": "type", "target": "Email", "text": "ada@example.com" }, { "do": "press", "text": "Enter" }] }, { "id": "s3", "pending": "Check your inbox screen" } ], "status": "ready", "createdAt": "2026-10-19T09:00:00.000Z" } ] } ``` | Step field | What it is | | --- | --- | | `storyId` | The page story shown on this step. | | `note` | What is happening on the screen, one sentence. | | `actions` | What the user does, in order: `{ "do": "click" \| "type" \| "press" \| "hover" \| "scroll" \| "wait", "target"?, "text"? }`. `text` is what to type, the key to press, or milliseconds to wait. The last action usually leads to the next step. | | `pending` | A screen that isn't built yet, described in words. | | `dream` | The id of a screen an agent is building; the step shows that story once it exists. | Agents save flows with the MCP tool `save_flow` or `npx stageboard flow save ''`. Both take `{ "flow"?, "name", "group", "description", "steps" }`, with steps written without an `id`; pass `flow` to update an existing flow. `flow save` also accepts a path to a JSON file and needs a running `npx stageboard dev`. See [MCP tools](/reference/mcp). ## Render a flow into one image `flow shot` renders every screen of a flow into one grid image, so you or an agent can take in the whole flow at once. It plays each step's actions before capturing the screen, and works without the dev server. ```bash npx stageboard flow shot "Sign up" --device mobile --theme dark ``` | Option | Default | What it does | | --- | --- | --- | | `` | required | The flow's id or name, or a unique part of the name. | | `--device` | `desktop` | `desktop`, `tablet` or `mobile`. | | `--theme` | none | `light` or `dark`. | | `--cols` | 3 desktop, 4 tablet, 5 mobile | Screens per row. | | `--out` | `.stageboard/shots/flow--.png`, with `-` when set | Output PNG path. | It prints the PNG path and then the flow as text: each step's title, story id and route, its note, and its actions marked `✓` when they played or `✗` with the reason when they failed. Run it before and after you change a flow. The MCP tool `flow_grid` does the same. # Next.js URL: https://docs.stageboard.design/guides/nextjs Render a Next.js app's components and pages in stageboard with the nextPreset Vite config. stageboard renders stories with Vite, not with Next.js. `nextPreset` from `stageboard/next` returns a Vite config that replaces the `next/*` modules with browser shims and copies the parts of Next's setup that components rely on (path aliases, `NEXT_PUBLIC_*` variables, `public/`). Use it for any App Router or Pages Router project. ## Config ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" import { nextPreset } from "stageboard/next" export default defineConfig({ stories: ["src/**/*.stories.tsx", "stageboard/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", vite: () => nextPreset({ root: __dirname }), agent: { provider: "claude" }, }) ``` ```tsx title="stageboard.preview.tsx" import "../src/app/globals.css" import type { Decorator } from "stageboard" import { ThemeProvider } from "../src/components/theme-provider" export const decorators: Decorator[] = [(Story) => ] ``` Tailwind v4 through `@tailwindcss/postcss` works as is, because Vite reads the project's PostCSS config. In global CSS, put `@import` lines before any `@plugin` line; Vite drops imports that come later. Server components that fetch data can't run in the browser. Write the page story against the client part of the page and pass it mock data, or mock the data layer in the preview's providers. ## Options | Option | Type | Default | What it does | |---|---|---|---| | `root` | `string` | `process.cwd()` | The Next.js project root. `tsconfig.json`, the `.env` files and `public/` are read from here. | | `aliases` | `Record` | `{}` | Extra import aliases, merged over the ones read from `tsconfig.json`. Keys match a whole path segment, so `"@lib"` matches `@lib` and `@lib/x`. | | `env` | `Record` | `{}` | Extra values for `process.env`, merged over the ones read from `.env` files. | To add your own Vite plugins on top, merge the preset with Vite's `mergeConfig`: ```ts title="stageboard.config.ts" import { mergeConfig } from "vite" import svgr from "vite-plugin-svgr" import { defineConfig } from "stageboard" import { nextPreset } from "stageboard/next" export default defineConfig({ stories: ["src/**/*.stories.tsx"], vite: () => mergeConfig(nextPreset({ root: __dirname }), { plugins: [svgr()] }), }) ``` ## What the preset does | Next.js feature | In stageboard | |---|---| | `next/image`, `next/legacy/image` | A plain ``. Static imports, `width`, `height` and `fill` work; `priority`, `quality`, `placeholder` and `loader` are ignored. | | `next/link` | An `` with the resolved `href`. A click doesn't leave the story: the shim cancels it and posts a `navigate` message to the board, which opens the screen whose `route` matches the `href` (in a flow, the matching step). | | `next/navigation`, `next/router` | `useRouter()` whose `push` and `replace` navigate the same way; `back`, `forward`, `refresh` and `prefetch` do nothing. `useParams()`, `usePathname()` and `useSearchParams()` follow the story's `parameters.page`: `params`, else the params matched from `path`; `path`, else the `route` with the params filled in. With neither, a dynamic route gives `{}` and the route as written. `redirect()` navigates and throws, `notFound()` throws. | | `next/font/google`, `next/font/local` | Each font call loads the family from Google Fonts and returns `style.fontFamily`. With a `variable` option it also returns a class that sets that CSS variable. | | `next/dynamic` | `React.lazy` with `Suspense`, showing the `loading` component while it loads. | | `next/script`, `next/head` | Render nothing. | | `tsconfig.json` `paths` | Become Vite aliases, so `@/components/x` resolves. | | `.env`, `.env.development`, `.env.local`, `.env.development.local` | Their `NEXT_PUBLIC_*` variables are available on `process.env`. `NODE_ENV` is `"development"`. | | `public/` | Served as Vite's public directory, so `/logo.svg` works. | | Directory imports | `import x from "./components/ui"` resolves to the folder's `index.ts(x)`. | Anything else from Next (server actions, `cookies()`, `headers()`, middleware) is not shimmed. Keep it out of the components you put on the board, or mock it in the preview. ## Next steps - [Screens](/guides/screens): add every route as a page story. - [Devtools in your app](/guides/devtools): the inspector in `next dev`, with a `rewrites()` example. # Screens and pages URL: https://docs.stageboard.design/guides/screens Turn page components into screens with parameters.page, browse them as Desktop, Tablet or Mobile, in light or dark, grouped by area. A screen is a story with `parameters.page`. It renders a whole page of your app with mock data, full-bleed, and shows up in the Screens view next to every other page. Use page stories to put your entire app on the board without running its backend. ```tsx title="src/pages/pages.stories.tsx" import type { Meta, Story } from "stageboard" import { HomePage, LoginPage, PricingPage } from "./pages" export default { title: "Pages", parameters: { layout: "fullscreen" } } satisfies Meta export const Home: Story = { render: () => , parameters: { page: { route: "/", title: "Home", group: "Marketing", links: ["/pricing", "/login"] } }, } export const Pricing: Story = { render: () => , parameters: { page: { route: "/pricing", title: "Pricing", group: "Marketing", links: ["/login"] } }, } export const Login: Story = { render: () => , parameters: { page: { route: "/login", title: "Log in", group: "Auth" } }, } ``` ## `parameters.page` | Option | Type | Default | What it does | | --- | --- | --- | --- | | `route` | `string` | required | The page's real route pattern, with dynamic segments in brackets: `/[team]/tickets/[id]`. A story is a screen only when it has a `route`. | | `title` | `string` | the story's name | Title on the screen card and in the sidebar. | | `group` | `string` | last part of the file's `title` | The section the screen is listed under. | | `links` | `string[]` | none | Routes this page navigates to. The single-screen view shows each linked screen as a button next to the title; agents read and update it when they build screens and flows. | | `params` | `Record` | from `path` | What `useParams()` returns in the story (Next.js shim). | | `path` | `string` | `route` with `params` filled in | The concrete URL the story shows, such as `/stays/42?tab=photos`, for `usePathname()` and `useSearchParams()`. | | `dream` | `string` | none | Set by an agent that built this page for a flow, so the placeholder in the flow becomes this story. Don't set it yourself. | A story with `page` defaults to the `fullscreen` layout. Wrap logged-in pages in a decorator that provides your app shell and mocks: ```tsx title="src/app/dashboard.stories.tsx" export const Tickets: Story = { render: () => , decorators: [(S) => ], parameters: { page: { route: "/[team]/dashboard/tickets", title: "Tickets", group: "Dashboard", links: ["/[team]/dashboard/tickets/[id]"], }, }, } ``` ## The Screens view Screens shows a card for every page story, rendered at the selected device size and grouped by `group`. The sidebar lists the same screens and searches their titles, story names and routes. Click a card to open the screen on its own, where you can [annotate](/guides/annotate) it, show its [variants](/guides/variants), open it in a new tab, or copy its `npx stageboard shot` command. Its `links` appear as buttons next to the title. A `next/link` click or `router.push` in the screen opens the screen whose `route` matches the URL (`/stays/42` matches `/stays/[id]` and `/stays/:id`; a literal route beats a dynamic one); with no match the board shows "No screen for /path". Components lists every story that isn't a screen, grouped by the file's `title`. A story that renders a whole page belongs under Screens, even when it only shows another state of a page that is already there: give it the same `route` and a `title` that names the state (see [States of a page](#states-of-a-page)). A story that spreads a screen story (`{ ...Home, args: { … } }`) or its `parameters` keeps the route and stays a screen. ## Devices The Device switch in the header renders every screen at one of three sizes: | Device | Size | | --- | --- | | Desktop | 1440 × 900 | | Tablet | 834 × 1194 | | Mobile | 390 × 844 | The same sizes are used by the flow viewer and `npx stageboard flow shot --device`. ## Light and dark The theme button next to the device switch cycles the screens through system, dark and light. A forced theme adds the `dark` or `light` class to ``, sets `color-scheme` and `data-theme`, and answers `prefers-color-scheme` queries with that theme, so class-based setups (Tailwind `dark:`, next-themes) and media-query setups both follow it. To pin one story to a theme, set `parameters.theme`: ```tsx title="src/pages/pages.stories.tsx" export const HomeDark: Story = { render: () => , parameters: { theme: "dark", page: { route: "/", title: "Home (dark)", group: "Marketing" } }, } ``` The theme chosen in the header wins over `parameters.theme`. From the command line, use `npx stageboard shot --theme dark` (see [CLI](/reference/cli)). ## States of a page Flows often need a page in a specific state: a dialog open, a form filled in, an empty inbox. Give each state its own page story with the same `route` and a distinct `title`, so it is a screen you can put in a flow. To cover a whole app, enumerate its routes, build the mock backend once, then add page stories area by area and check each with `npx stageboard shot --width 1440 --full`. # Stories URL: https://docs.stageboard.design/guides/stories Write stories for your components and pages, wrap them in providers and mocks with a preview file, and know how story ids are made. A story is one state of a component or page, rendered with mock data. Stories live in `*.stories.tsx` files that match the `stories` globs in [`stageboard.config.ts`](/configuration), and stageboard shows them under Screens and Components. ```tsx title="src/components/button.stories.tsx" import type { Meta, Story } from "stageboard" import { Button } from "./button" export default { title: "Primitives/Button", component: Button, args: { children: "Save" }, } satisfies Meta export const Primary: Story = {} export const Disabled: Story = { args: { disabled: true } } ``` This file has two stories, `primitives-button--primary` and `primitives-button--disabled`. ## The default export: `Meta` The default export describes every story in the file. Import the `Meta` type from `stageboard` and use `satisfies Meta`, so `args` are checked against the component's props. | Field | Type | What it does | | --- | --- | --- | | `title` | `string` | Group and name in the sidebar, with `/` for nesting. Also the first half of every story id. | | `component` | component | Rendered with the merged `args` when a story has no `render`. | | `args` | `Partial` | Props shared by all stories in the file. | | `parameters` | object | Defaults for every story in the file (see below). | | `decorators` | `Decorator[]` | Wrappers for every story in the file. | `parameters` takes these fields: | Option | Type | Default | What it does | | --- | --- | --- | --- | | `layout` | `"centered" \| "padded" \| "fullscreen"` | `"fullscreen"` with `page`, otherwise `"centered"` | How the story sits in the frame. | | `viewport` | `{ width, height? }` | `640 × 360` for components | The frame a component story renders in. A missing `height` is 16:9 to the width. See [Component frames](#component-frames). | | `theme` | `"light" \| "dark"` | none | Forces the color scheme (see [Screens](/guides/screens#light-and-dark)). | | `background` | `string` | none | CSS background of the frame's body. | | `page` | object | none | Makes the story a screen. See [Screens](/guides/screens). | | `variants` | `Record` | none | Variant to show per boundary, as `{ "landing.hero": "hero.calm" }`. See [Variants](/guides/variants). | ## Component frames A component story renders in its own frame, `parameters.viewport`, not at the Desktop, Tablet or Mobile size the device switcher sets for screens. Components shows every frame scaled to fit the same 16:9 tile, so a small button fills its tile as well as a whole panel does. The flow-style viewer and `stageboard shot` render the story at its frame too. Inside the frame, the story is a page of that size, so its CSS breakpoints apply to the frame's width. Set the frame on the default export for all of a file's stories, and on a story that needs a different one: ```tsx title="src/components/button.stories.tsx" export default { title: "Primitives/Button", component: Button, parameters: { viewport: { width: 880, height: 120 } }, } satisfies Meta export const Variants: Story = { render: () => } export const Primary: Story = { parameters: { viewport: { width: 200, height: 120 } } } ``` Make a frame just large enough for what the story draws, plus 40px on each side, and leave room for a menu, popover or dialog it opens. `npx stageboard frames` measures every component story and prints frames that fit; `--write` sets them in the story files. Write the frame as numbers, so stageboard can read it without loading the file. ## Named exports: `Story` Every named export that starts with an uppercase letter is a story. Exports that start with a lowercase letter, or with `__`, are ignored, so helpers and fixtures can live in the same file. | Field | Type | What it does | | --- | --- | --- | | `name` | `string` | Display name. Defaults to the export name split into words (`LongTitle` becomes "Long Title"). | | `args` | `Partial` | Merged over the file's `args`. | | `parameters` | object | Merged over the file's `parameters`, one level deep. | | `decorators` | `Decorator[]` | Wrappers for this story only. | | `render` | `(args, context) => ReactNode` | Renders the story instead of `component`. `context` has `id`, `title`, `name`, `args` and `parameters`. | A story can also be a function; it is used as `render`: ```tsx title="src/pages/pages.stories.tsx" export const Empty = () => ``` ## The preview file The preview file wraps every story. Point the `preview` option at it (`npx stageboard init` writes `./stageboard.preview.tsx`). It imports global CSS and exports `decorators`, and optionally `parameters` that act as defaults below the file's and the story's own. ```tsx title="stageboard.preview.tsx" import "./src/styles.css" import type { Decorator } from "stageboard" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import { MockAuthProvider } from "./stageboard/mocks" const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) export const decorators: Decorator[] = [ (Story) => ( ), ] ``` A decorator is `(Story, context) => ReactNode`. Story decorators wrap the story first, then the file's, then the preview's, so the preview's are outermost. ## Mock data Pages need data without a backend or login. Mock at the edges, not inside components: - **Providers.** Keep pure UI providers (theme, query client, i18n). Replace anything that authenticates, opens sockets or redirects with the same React context and a static value. - **HTTP.** Mock the client once: an axios `adapter`, a wrapper around `window.fetch` for your API paths, or handlers keyed by operation name for generated clients. Unknown endpoints should return an empty value of the right shape, so a page never hangs on a loader. - **Fixtures.** Write factories (`makeTicket`) that fill every required field, and use one consistent fictional world with realistic names and dates in your product's language. Avoid lorem ipsum. For Next.js apps, `nextPreset` shims `next/image`, `next/link`, `next/navigation`, `next/font` and `next/dynamic`; see [Next.js](/guides/nextjs). ## Story ids An id is `kebab(title)--kebab(exportName)`. `Marketing/Hero` with `export const LongTitle` becomes `marketing-hero--long-title`. The `name` field does not change the id. Run `npx stageboard list` to see every id. Without a `title`, stageboard derives one from the file path: it drops the `.stories.tsx` extension and the `src`, `components` and `app` folders, collapses a repeated segment, drops a trailing `index`, and title-cases the rest. `src/components/button/button.stories.tsx` becomes `Button`, and `src/features/billing-plan/index.stories.tsx` becomes `Features/Billing Plan`. stageboard reads titles from the source text without running it, so write `title` as a string literal, either in `export default { … }` or in a `const meta = { … }` that the file exports with `export default meta`. ## Storybook compatibility The format follows Storybook's Component Story Format: a default export with `title`, `component`, `args`, `parameters` and `decorators`, and named object stories with `args`, `render` and `name`. Most CSF 3 stories render unchanged once their types come from `stageboard`. What stageboard does not do: - No `play` functions, `loaders`, `argTypes`, controls, globals or addons. To script what a user does on a screen, use [flow actions](/guides/flows). - `tags` is accepted but ignored. - `parameters` other than the ones listed above are passed through to `context.parameters` but have no effect of their own. - Titles and ids come from the source text, so computed titles and re-exported stories are not indexed. # Variants URL: https://docs.stageboard.design/guides/variants Make a component swappable, add alternatives as *.variant.tsx files, switch between them on the live screen and screenshot each one. A variant is an alternative implementation of one part of your UI, kept in its own `*.variant.tsx` file. You mark a component as swappable once; then you or an agent add variants, and you switch between them on the live screen to compare. Production always renders the original. ## Make a component swappable Wrap the implementation with `variant(id, Component)` from `stageboard/react` and export the result under the original name: ```tsx title="src/components/hero.tsx" import { variant } from "stageboard/react" export type HeroProps = { title: string; subtitle: string; cta: string } function HeroImpl({ title, subtitle, cta }: HeroProps) { return ( ) } export const Hero = variant("home.hero", HeroImpl) ``` For a section of inline JSX, wrap it in ``. A selected variant's default export replaces the children and receives the `props` you pass: ```tsx title="src/pages/home.tsx" import { Variant } from "stageboard/react" ``` Use stable, dotted ids such as `.`. When `process.env.NODE_ENV` is `"production"`, `variant()` returns the original component and `` renders its children, so nothing of stageboard runs in production. See [React API](/reference/react). ## Write a variant file Each alternative is one file next to the original, named `..variant.tsx`. It exports `variant` metadata and a default component with the same props: ```tsx title="src/components/hero.split.variant.tsx" import type { HeroProps } from "./hero" export const variant = { of: "home.hero", label: "Split with panel", description: "Left-aligned copy with a tinted product panel on the right.", } export default function HeroSplit({ title, subtitle, cta }: HeroProps) { return (

{title}

{subtitle}

{cta}
) } ``` | Field | Type | Default | What it does | | --- | --- | --- | --- | | `of` | `string` | required | The id passed to `variant()` or ``. Files without it are skipped. | | `label` | `string` | the key | Name shown in the variant menu. | | `description` | `string` | none | One sentence on what is different. | | `job` | `string` | none | Set by an agent to the request it was made for. | The variant's **key** is its file name without `.variant.tsx`: `hero.split.variant.tsx` has the key `hero.split`. stageboard reads the `variant` object from the source text, so keep its values as plain string literals. ## The registry file stageboard finds variant files with the `variantGlobs` option (default `**/*.variant.{tsx,jsx,ts,js}`) and writes a registry module that imports them all. It is `stageboard.variants.ts` next to your config, or the path in the `variantsFile` option (see [Configuration](/configuration)). ```ts title="stageboard.variants.ts" // Generated by stageboard. Do not edit; it is rewritten whenever *.variant.tsx files change. import { registerVariants } from "stageboard/react" import * as v0 from "./src/components/hero.split.variant" registerVariants([ { file: "src/components/hero.split.variant.tsx", module: v0 }, ]) ``` `npx stageboard dev` rewrites it whenever a variant file is added, changed or removed. To regenerate it by hand, run: ```bash npx stageboard variants ``` Commit the file: stageboard's own frames load it automatically, and your app imports it so the [in-app devtools](/guides/devtools) can switch variants too. ## Switch variants on the live screen Open a screen on its own and choose Variants in the toolbar; the count shows how many variants the screen has. Every swappable part is outlined in yellow, labelled with its id and the variant it shows. Click an outline to open its menu: Original is what's in the code today, followed by every variant with its description. Pick one and the screen re-renders with it. The flow viewer has the same Variants button. Variants that an agent is still writing are counted in the menu and appear as soon as their files exist. Ask for more variants in the menu opens a note to request more alternatives of that part. You can also ask for variants from any [annotation](/guides/annotate) by choosing Variants instead of Edit. Your choice is kept by the dev server and in the browser, and applies to every screen that renders that id. It never changes production. To keep a variant for good, pick it and press Adopt as original in the menu (or in the in-app inspector). An agent ports the variant into the original component, keeps the `variant()` boundary and its id, and deletes the variant file; its pin shows Adopting, then Adopted. To drop a variant, delete its file. To pin a variant in one story, set `parameters.variants`: ```tsx title="src/components/hero.stories.tsx" export const Split: Story = { parameters: { variants: { "home.hero": "hero.split" } } } ``` ## Screenshot a variant `shot` takes `--variant =`, once per boundary: ```bash npx stageboard shot marketing-hero--default --variant home.hero=hero.split --width 1440 --out /tmp/hero-split.png ``` Agents compare all variants of one boundary with the MCP tool `compare_variants`, list them with `list_variants`, and switch the one you see with `select_variant`. See [CLI](/reference/cli) and [MCP tools](/reference/mcp). # Introduction URL: https://docs.stageboard.design/ Learn what stageboard is, who it is for and how its annotate-and-fix loop works. stageboard puts every screen, flow and component of a React app on one board. It renders your real components from Storybook-compatible stories with mock data, so no backend or login is needed. People annotate what they see, and an agent changes the real code, optionally as live variants you can switch between. ## Who it is for - Teams building a React app (Vite or Next.js) with a coding agent such as Claude Code, Codex, atmOS or any CLI agent. - Designers, product people and reviewers who want to point at a screen and say what is wrong, without opening the code. - Agents themselves: `npx stageboard shot` and the MCP server let an agent screenshot any story and check its own work. ## How it differs from a design canvas A design canvas holds drawings of your app. stageboard holds the app. Every frame on the board is your actual component, rendered with your CSS, your providers and mock data. A change requested on the board is made in your source files, so there is nothing to hand off and nothing drifts out of sync. ## The core loop 1. **Describe the app.** Write `*.stories.tsx` files for components and pages. They show up under Components and Screens, and ordered journeys through the screens become Flows. 2. **Annotate.** Press A, drag over any screen, component or variant, and write a note. Paste reference images or sketch on the screen. 3. **Send it to an agent.** The agent gets the note, a cropped screenshot, the component chain with source paths and the variants in view, and edits the real code. 4. **Pick the result.** The change appears live. If the agent wrote variants (`*.variant.tsx`), you choose one or keep the original. ## Next - [Quick start](/quick-start): install stageboard and see your first story. - [Configuration](/configuration): every field of `stageboard.config.ts`. - [Stories](/guides/stories), [Screens](/guides/screens) and [Flows](/guides/flows): put your app on the board. - [Annotate](/guides/annotate), [Variants](/guides/variants) and [Agents](/guides/agents): get changes made. - [Next.js](/guides/nextjs) and [Devtools in your app](/guides/devtools): framework setup and the in-app inspector. # Quick start URL: https://docs.stageboard.design/quick-start Install stageboard, write a first story and open the board, either through your agent or by hand. The fastest way to set up stageboard is to let your coding agent do it. The manual path below does the same steps by hand. ## Let your agent set it up Paste this prompt into Claude Code, Codex, atmOS or any agent that reads `.claude/skills` or `.agents/skills`: ```text title="Prompt" Set up stageboard in this repo. Run `npm i -D stageboard` (use this repo's package manager), then `npx stageboard skill` to install the stageboard skill, and follow it: write the config and a preview with mock providers, add stories for the main components, put every page in Screens with realistic mock data, and check each one with `npx stageboard shot`. Finish by listing the stories that look weak. ``` The skill tells the agent how to write the config, mock providers, stories and page stories, and to check each one with a screenshot. ## Set it up by hand ### 1. Install ```npm npm i -D stageboard ``` stageboard needs `react` and `react-dom` 18 or later. It runs on Vite 6 or later. ### 2. Create the config ```bash npx stageboard init ``` This writes `stageboard.config.ts` in the current directory: ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", agent: { provider: "claude" }, }) ``` Create the preview file it points to. It imports your global CSS and exports `decorators` that wrap every story, for example in your providers: ```tsx title="stageboard.preview.tsx" import "./src/styles.css" import type { Decorator } from "stageboard" export const decorators: Decorator[] = [(Story) =>
] ``` Add `.stageboard/` (local state and caches) to `.gitignore`. See [Configuration](/configuration) for every option. ### 3. Write a first story ```tsx title="src/components/button.stories.tsx" import type { Meta, Story } from "stageboard" import { Button } from "./button" export default { title: "Primitives/Button", component: Button, args: { children: "Save" } } satisfies Meta export const Primary: Story = {} export const Disabled: Story = { args: { disabled: true } } ``` Story ids are `kebab(title)--kebab(export)`, so these are `primitives-button--primary` and `primitives-button--disabled`. `npx stageboard list` prints them. See [Stories](/guides/stories) for the full format. ### 4. Start the board ```bash npx stageboard dev ``` Open `http://localhost:6060/__stageboard/`. The port defaults to `6060` (or `STAGEBOARD_PORT`), and stageboard moves to the next free port if it is taken; the terminal prints the actual URL. Use `--port` and `--host` to change them. To check a story without opening the board, take a screenshot: ```bash npx stageboard shot primitives-button--primary --width 1440 --out /tmp/button.png ``` ### 5. Install the agent skill ```bash npx stageboard skill ``` This copies the stageboard skill into `.claude/skills/stageboard` and `.agents/skills/stageboard`. Pass `--claude` or `--codex` to install only one of them. Run it again after upgrading stageboard. ## Next steps - [Stories](/guides/stories) and [Screens](/guides/screens): cover your components and every page. - [Flows](/guides/flows): string screens into user journeys. - [Annotate](/guides/annotate): point at what is wrong and send it to an agent. - [Agents](/guides/agents): pick Claude Code, Codex, atmOS or your own CLI. - [Next.js](/guides/nextjs): use the Next.js preset. - [CLI reference](/reference/cli): every command and flag. # CLI URL: https://docs.stageboard.design/reference/cli Every stageboard command and option, with a usage line and an example for each. The `stageboard` CLI starts the board, renders stories and flows to PNGs, runs the MCP server and installs the agent skill. Run it with `npx stageboard `; `npx stageboard --help` and `npx stageboard --help` list the options. Every command except `init` and `skill` reads `stageboard.config.ts` (or `.mts`, `.js`, `.mjs`), searching upward from the current folder. | Global option | What it does | | --- | --- | | `--config ` | Use this config file instead of searching for one. | | `-h`, `--help` | Show help. | | `-v`, `--version` | Show the version. | ## dev ```text stageboard dev [--port ] [--host ] ``` Starts the board (Screens, Flows, Components), the agent bridge and the Vite dev server, and prints the catalog URL, the public URL if set, and the agent provider. | Option | Default | What it does | | --- | --- | --- | | `--port ` | `port` from the config, else `STAGEBOARD_PORT`, else `6060` | Port to listen on. If it is taken, the next free one is used. | | `--host ` | all interfaces | Host to bind. | The board is at `http://localhost:6060/__stageboard/`. `STAGEBOARD_PUBLIC_URL` (or `publicUrl` in the config) sets the URL printed as `public` and used for hot reload behind a proxy. ```bash npx stageboard dev --port 7070 ``` ## shot ```text stageboard shot [--variant ]... [--width ] [--height ] [--full] [--theme ] [--scale ] [--out ] ``` Renders one story in headless Chromium and writes a PNG. `` is a story id, `file#Export` (for example `src/hero.stories.tsx#Default`), or a search term that matches exactly one story. It uses the running `stageboard dev` server if there is one, and starts a temporary one otherwise, so the app does not need to run. | Option | Default | What it does | | --- | --- | --- | | `--variant ` | none | Render variant `key` for boundary `id`. Repeat for several boundaries. | | `--width ` | the story's frame, else `1280` | Viewport width. | | `--height ` | the story's frame, else `800` | Viewport height. | | `--full` | off | Capture the full scroll height of the page. | | `--theme ` | none | `light` or `dark` color scheme. | | `--scale ` | `1` | Device scale factor. | | `--out ` | `.stageboard/shots/.png` | Output PNG path. | It prints the PNG path on stdout, and console errors from the story on stderr. Without `--full`, it clips to the story's stage unless the story's layout is `fullscreen`. Chromium is found from Playwright, `STAGEBOARD_CHROME`, `CHROME_PATH` or a system install; if none exists, run `npx playwright install chromium`. ```bash npx stageboard shot landing-hero--default --variant landing.hero=hero.calm --width 1440 --full --out /tmp/hero.png ``` ## flow ```text stageboard flow shot [--device ] [--theme ] [--cols ] [--out ] stageboard flow save ``` `flow shot` renders every screen of a flow, in order, into one grid image. `` is a flow id or name (a unique part of the name works). Before capturing a step, it plays the step's actions. It prints the PNG path, then the flow as text: each step's title, story id, route and note, and every action marked `✓` (played), `✗` (failed, with the reason) or `·` (not played). | Option | Default | What it does | | --- | --- | --- | | `--device ` | `desktop` | `desktop` (1440×900), `tablet` (834×1194) or `mobile` (390×844). | | `--theme ` | none | `light` or `dark`. | | `--cols ` | 3 desktop, 4 tablet, 5 mobile | Screens per row. | | `--out ` | `.stageboard/shots/flow--.png` | Output PNG path (`-` is added to the default name when set). | ```bash npx stageboard flow shot "Sign up" --device mobile ``` `flow save` saves a flow to `stageboard/board.json` through the running `stageboard dev` server. The argument is the JSON itself or a path to a JSON file. Pass `"flow": ""` to update a flow; omit it to add one. The fields are the same as the MCP tool [`save_flow`](/reference/mcp#save_flow). ```bash npx stageboard flow save '{ "name": "Sign up", "group": "Onboarding", "steps": [{ "storyId": "pages-signup--default", "note": "The user enters an email", "actions": [{ "do": "click", "target": "Continue" }] }, { "pending": "Check your inbox" }] }' ``` See [Flows](/guides/flows). ## frames ```text stageboard frames [filter] [--write] ``` Measures what every component story draws and gives it a frame, `parameters.viewport`, that fits: the content plus 40px on each side, with room for a menu, popover or dialog it opens, grown if the story lays out larger at that size. Each file gets one frame that fits all its stories, and a story much smaller than that gets its own. `filter` matches id, title or file. Like `shot`, it uses the running `stageboard dev` server or starts a temporary one. See [Component frames](/guides/stories#component-frames). | Option | What it does | | --- | --- | | `--write` | Write the frames into the story files. Frames that are already set stay as they are. Without it, the frames are only printed. | ```bash npx stageboard frames --write ``` ## list ```text stageboard list [filter] [--json] ``` Lists every story: its id, its file, and `page` for page stories. `filter` matches id, title or name, case-insensitive. | Option | What it does | | --- | --- | | `--json` | Print the stories as JSON: `id`, `title`, `name`, `exportName`, `file`, `hasPage`, the frame (`viewport`) when the story sets one and, for page stories, `page`. | ```bash npx stageboard list hero ``` ## mcp ```text stageboard mcp ``` Runs the stageboard MCP server on stdio. See [MCP server](/reference/mcp). ```bash npx stageboard mcp ``` ## done ```text stageboard done [summary] [--failed] ``` Tells the running `stageboard dev` server that an agent job is finished, so the board stops showing it as running. Agents run this as their last step when they don't have the MCP tool `complete_job`. | Option | What it does | | --- | --- | | `--failed` | The job could not be done. | ```bash npx stageboard done 3f9c2a1b "Made the hero title two lines on mobile" ``` ## skill ```text stageboard skill [dir] [--claude] [--codex] ``` Installs the stageboard agent skill into `.claude/skills/stageboard` and `.agents/skills/stageboard` under `dir` (default: the current folder), replacing an existing copy. | Option | What it does | | --- | --- | | `--claude` | Only install into `.claude/skills`. | | `--codex` | Only install into `.agents/skills`. | ```bash npx stageboard skill --claude ``` ## variants ```text stageboard variants ``` Regenerates the variants registry file (`variantsFile`, default `stageboard.variants.ts`) from the `*.variant.tsx` files. `stageboard dev` does this on its own; run it after adding or removing variants without the dev server. ```bash npx stageboard variants ``` See [Variants](/guides/variants). ## init ```text stageboard init ``` Creates `stageboard.config.ts` in the current folder, unless one already exists: ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", agent: { provider: "claude" }, }) ``` ```bash npx stageboard init ``` See [Configuration](/configuration). # Docs for agents URL: https://docs.stageboard.design/reference/llms Point an agent at the plain-text versions of these docs, or install the stageboard skill in your repo. These docs are also published as plain Markdown for language models: | URL | What it contains | | --- | --- | | [`/llms.txt`](https://docs.stageboard.design/llms.txt) | A short description of stageboard and a link to every page's Markdown, grouped like the sidebar. Start here. | | [`/llms-full.txt`](https://docs.stageboard.design/llms-full.txt) | Every page in full, as Markdown, in one file. | | `/.md` | One page as Markdown, for example [`/guides/flows.md`](https://docs.stageboard.design/guides/flows.md). The introduction is `/index.md`. `.mdx` works too. | Every page also has Copy Markdown and Open in ChatGPT or Claude buttons under its title. A request with `Accept: text/markdown` to a page's normal URL gets the Markdown as well. To use them, give the agent the URL and ask it to read it before it works: ```text Read https://docs.stageboard.design/llms-full.txt, then set up stageboard in this repo. ``` Agents that fetch web pages (Claude Code, Codex and most others) follow the URL themselves. For a tool that only takes files, download one first, for example `curl -o stageboard-docs.md https://docs.stageboard.design/llms-full.txt`. ## Inside a repo, use the skill For an agent working in your project, the stageboard skill is better than the docs: it is written as instructions for agents, ships with the installed version of stageboard, and is picked up automatically from the repo. ```bash npx stageboard skill ``` This installs it into `.claude/skills/stageboard` and `.agents/skills/stageboard`. Every request stageboard sends to an agent tells it to follow the skill when it is installed. See [Agents](/guides/agents#the-skill) and [CLI](/reference/cli#skill). # MCP server URL: https://docs.stageboard.design/reference/mcp Run the stageboard MCP server, connect it to an agent, and use its tools to list, screenshot and compare stories, variants and flows. The stageboard MCP server gives an agent tools to list stories, screenshot them, compare and select variants, save and render flows, and report a job finished. It runs on stdio: ```bash npx stageboard mcp ``` It finds `stageboard.config.ts` from its working directory (or the file passed with `--config`). The tools talk to the running `stageboard dev` server; if none is running, the MCP server starts a temporary one on `127.0.0.1`, so the app does not need to run. ## Attached automatically When stageboard sends a request to the `claude` or `codex` provider, it attaches its MCP server to that agent run under the name `stageboard`: - `claude` gets `--mcp-config '{"mcpServers":{"stageboard":{...}}}'`. - `codex` gets `-c mcp_servers.stageboard.command=...` and `-c mcp_servers.stageboard.args=...`. The server runs as `node /dist/node/cli.js mcp --config `, from the installed `stageboard` package. The `atmos` and `command` providers don't get it; their agents use the `npx stageboard` CLI instead, or a server you add yourself. See [Agents](/guides/agents). ## Adding it to an MCP client For any client that reads an `mcpServers` config, add: ```json title=".mcp.json" { "mcpServers": { "stageboard": { "command": "npx", "args": ["stageboard", "mcp"] } } } ``` Run the client from the project folder, or add `"--config", "/abs/path/stageboard.config.ts"` to `args`. For Claude Code, `claude mcp add stageboard -- npx stageboard mcp` does the same. For Codex: ```toml title="~/.codex/config.toml" [mcp_servers.stageboard] command = "npx" args = ["stageboard", "mcp"] ``` ## Tools ### list_stories Lists the stories (components and pages) stageboard can render, with the ids to pass to `screenshot`. | Parameter | Type | What it does | | --- | --- | --- | | `filter` | `string`, optional | Case-insensitive text to match against id, title or name. | Returns text, one story per line: `id`, `title / name`, `file` and `page` for page stories, separated by tabs. Returns `No stories match.` if nothing matches. ### screenshot Renders one story in a headless browser and returns a PNG. | Parameter | Type | What it does | | --- | --- | --- | | `story` | `string` | Story id from `list_stories`. | | `variants` | `Record`, optional | Variant selections by boundary id, for example `{ "landing.hero": "hero.calm" }`. | | `width` | `integer`, optional | Viewport width. | | `height` | `integer`, optional | Viewport height. | | `full` | `boolean`, optional | Capture the full scroll height. | | `region` | `{ x, y, width, height }`, optional | Clip to this region, in story viewport pixels. | Returns one `image/png` image. ### list_variants Lists variant boundaries, their variant files and which one is selected. | Parameter | Type | What it does | | --- | --- | --- | | `id` | `string`, optional | Only this boundary id, for example `landing.hero`. | Returns JSON: `[{ "id", "selected", "variants": [{ "key", "label", "description", "file" }] }]`. `selected` is `"original"` when no variant is selected. ### compare_variants Screenshots a story once per variant of one boundary, original first, so you can compare them side by side. | Parameter | Type | What it does | | --- | --- | --- | | `id` | `string` | Boundary id, for example `landing.hero`. | | `story` | `string`, optional | Story to render. Defaults to a story that renders the boundary. | | `keys` | `string[]`, optional | Only these variant keys (`"original"` allowed). | | `width` | `integer`, optional | Viewport width. | | `full` | `boolean`, optional | Capture the full scroll height. | Returns, per variant, a text label `Label (key)` followed by its PNG. Fails if no story renders the boundary and `story` is not passed. ### select_variant Selects which variant the user sees for a boundary. Updates the live app, Components and Screens. | Parameter | Type | What it does | | --- | --- | --- | | `id` | `string` | Boundary id. | | `key` | `string` | Variant key, or `"original"` for the committed component. | Returns `Selected for .` ### complete_job Tells stageboard the agent has finished a job it was given. Every stageboard prompt contains the job id and asks the agent to call this once, as its last step, so the board stops showing the job as running. | Parameter | Type | What it does | | --- | --- | --- | | `job` | `string` | Job id from the prompt. | | `summary` | `string` | One or two sentences on what changed. | | `failed` | `boolean`, optional | `true` if the agent could not do what was asked. | Returns `Job marked done.` or `Job marked failed.` The CLI equivalent is `npx stageboard done "" [--failed]`. ### save_flow Saves a user flow for the Flows view to `stageboard/board.json`: an ordered list of screens. | Parameter | Type | What it does | | --- | --- | --- | | `flow` | `string`, optional | Flow id to update. Omit to add a new flow. | | `name` | `string`, optional | Flow name. | | `group` | `string`, optional | Section in the Flows sidebar, for example `Onboarding`. | | `description` | `string`, optional | What the flow is about. | | `steps` | `Step[]` | The screens, in the order the user sees them. | Each step has: | Field | Type | What it does | | --- | --- | --- | | `storyId` | `string`, optional | Page story id of the screen. | | `pending` | `string`, optional | A screen that is still being built, instead of `storyId`. | | `note` | `string`, optional | What is happening on this screen, one short sentence. | | `actions` | `Action[]`, optional | What the user does on this screen, in order. The viewer plays them with a cursor; the last one usually leads to the next step. | An action is `{ "do": "click" | "type" | "press" | "hover" | "scroll" | "wait", "target"?: string, "text"?: string }`. `target` is the element's visible text, aria-label, placeholder or label, or a CSS selector. `text` is the text to type, the key to press, or milliseconds to wait. ```json { "name": "Sign up", "group": "Onboarding", "steps": [ { "storyId": "pages-signup--default", "note": "The user enters an email", "actions": [{ "do": "type", "target": "Email", "text": "ana@example.com" }, { "do": "click", "target": "Continue" }] }, { "pending": "Check your inbox" } ] } ``` Returns `Saved flow with steps.` ### flow_grid Renders every screen of a flow, in order, into one grid image, playing each step's actions before capturing it. Use it to take in a whole flow at once and to check it after changing it. | Parameter | Type | What it does | | --- | --- | --- | | `flow` | `string` | Flow id or name from `list_flows`. | | `device` | `"desktop" \| "tablet" \| "mobile"`, optional | Screen size. Defaults to `desktop`. | | `theme` | `"light" \| "dark"`, optional | Color scheme. | Returns the flow as text (each step's title, story id, route, note and actions, with `✓` for actions that played and `✗` with the reason for ones that failed), the path of the saved PNG, and the PNG itself. The CLI equivalent is `npx stageboard flow shot`. ### list_flows Lists the user flows with their steps. Takes no parameters. Returns JSON: every flow with its `id`, `name`, `group`, `description`, `status` (`ready`, `assembling` or `failed`) and `steps` (each with its own `id`, plus `storyId` or `pending`, `note` and `actions`). See [Flows](/guides/flows), [Variants](/guides/variants) and [CLI](/reference/cli). # API reference URL: https://docs.stageboard.design/reference/react The public exports of stageboard, stageboard/react, stageboard/next and stageboard/node, with signatures and what each is for. stageboard has four public entry points. App authors mostly use `stageboard` (config and story types), `stageboard/react` (variants and the in-app inspector) and `stageboard/next` (the Next.js preset). `stageboard/node` is for scripts that drive stageboard from Node. ## stageboard Config and story types. Safe to import anywhere; it has no runtime dependencies. ### defineConfig ```ts function defineConfig(config: StageboardConfig): StageboardConfig ``` Returns the config unchanged and types it. Default-export it from `stageboard.config.ts`. See [Configuration](/configuration) for every option and [Agents](/guides/agents) for `agent`. ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" export default defineConfig({ stories: ["src/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", agent: { provider: "claude" }, }) ``` ### Meta ```ts type Meta> = { title?: string component?: C args?: Partial> parameters?: StageParameters decorators?: Decorator>[] tags?: string[] } ``` The default export of a `*.stories.tsx` file. Without `title`, the title comes from the file path. ### Story ```ts type Story> = { name?: string args?: Partial> parameters?: StageParameters decorators?: Decorator>[] render?: (args: PropsOf, context: StoryContext>) => ReactNode tags?: string[] } ``` A named export of a stories file. The story id is `kebab(title)--kebab(exportName)`. ```tsx title="src/button.stories.tsx" import type { Meta, Story } from "stageboard" import { Button } from "./button" export default { title: "Primitives/Button", component: Button, args: { children: "Save" } } satisfies Meta export const Primary: Story = {} export const Disabled: Story = { args: { disabled: true } } ``` See [Stories](/guides/stories). ### Decorator ```ts type Decorator> = (Story: ComponentType, context: StoryContext) => ReactNode ``` Wraps a story, for example in providers. Story decorators run innermost, then the meta's, then the `decorators` exported from the preview file. ### StageParameters and PageParameters ```ts type StageParameters = { layout?: "centered" | "padded" | "fullscreen" viewport?: { width: number; height?: number } theme?: "light" | "dark" background?: string page?: PageParameters variants?: Record [key: string]: unknown } type PageParameters = { route: string; title?: string; links?: string[]; group?: string; dream?: string } ``` `parameters` of a meta or story. `layout` defaults to `fullscreen` for page stories and `centered` otherwise. `page` makes a story a screen in Screens (see [Screens and pages](/guides/screens)). `variants` pins boundary ids to variant keys for this story. ### Other exports `StageboardConfig` and the `AgentConfig` types (`ClaudeAgentConfig`, `CodexAgentConfig`, `AtmosAgentConfig`, `CommandAgentConfig`) type the config. `StoryContext` is the second argument of `render` and decorators. The entry point also exports the wire protocol shared by the board and the server (`Job`, `ChangeRequest`, `Flow`, `FlowStep`, `FlowAction`, `API_BASE`, `DEVICES` and similar) and story id helpers (`toStoryId`, `kebab`, `titleFromPath`); you rarely need them. ## stageboard/react Runtime for your app: variant boundaries, the variant registry and the in-app inspector. In production builds (`NODE_ENV=production`) `variant` and `Variant` render the original and nothing else. ### variant ```ts function variant

(id: string, Original: ComponentType

): ComponentType

``` Makes a component swappable. `id` is a stable dotted id such as `landing.hero`. The returned component renders the selected variant for that id, or `Original`. ```tsx title="src/landing/hero.tsx" import { variant } from "stageboard/react" function HeroImpl(props: HeroProps) { return

{props.title}
} export const Hero = variant("landing.hero", HeroImpl) ``` ### Variant ```ts function Variant(props: { id: string; children?: ReactNode; props?: Record }): JSX.Element ``` The same for an inline JSX section: renders `children`, or the selected variant's default export with `props`. ```tsx ``` See [Variants](/guides/variants). ### StageboardDevtools and mountDevtools ```ts type DevtoolsOptions = { base?: string; embedded?: boolean; hotkey?: string } function StageboardDevtools(props: DevtoolsOptions): null function mountDevtools(options?: DevtoolsOptions): () => void ``` Mount the stageboard inspector in your running app, in development. `StageboardDevtools` is the React component; `mountDevtools` does the same without React and returns an unmount function. `base` is where the stageboard server is reachable from the app (default `/__stageboard`, which you proxy to the stageboard server). `embedded` is set automatically when the page runs inside the board (`?sb-embed`). `hotkey` is accepted but not used yet. ```tsx title="app/stageboard-dev.tsx" "use client" import "@/stageboard.variants" import { StageboardDevtools } from "stageboard/react" export default function StageboardDev() { return } ``` See [Devtools in your app](/guides/devtools). ### Variant registry and state | Export | Signature | What it does | | --- | --- | --- | | `registerVariants` | `(entries: { file: string; module: VariantModule }[]) => void` | Registers variant files. The generated `stageboard.variants.ts` calls it; import that file once in your app. | | `useSelectedVariant` | `(id: string) => RegisteredVariant \| undefined` | The variant selected for a boundary, or `undefined` for the original. | | `selectVariant` | `(id: string, key: string) => Promise` | Selects a variant (`"original"` for none) and tells the server. | | `setVariantOverrides` | `(overrides: Record) => void` | Forces selections locally, for example in tests. URL overrides still win. | | `useStageboard` | `(select: (s: Snapshot) => T) => T` | Reads the client store: selections, jobs, flows, registered variants, connection state. | The remaining exports (`submitChangeRequest`, `cancelJob`, `createDream`, `updateDream`, `removeDream`, `createFlow`, `updateFlow`, `removeFlow`, `startBridge`, `setBridgeBase`, `describeElement`, `componentChain`, `nearestVariant`, `describeRegion`, `variantBoundaryRects`, `installSketch`, `Panel`, `devtoolsCss`, `StageboardVariant`) are what the board and the inspector are built from. ## stageboard/next ### nextPreset ```ts type NextPresetOptions = { root?: string; aliases?: Record; env?: Record } function nextPreset(options?: NextPresetOptions): InlineConfig ``` Returns a Vite config that renders a Next.js app's components outside Next: it shims `next/image`, `next/legacy/image`, `next/link`, `next/navigation`, `next/router`, `next/dynamic`, `next/script`, `next/head` and `next/font`, resolves the tsconfig path aliases, loads the `.env` files into `process.env`, and serves `public/`. `root` defaults to the current folder; `aliases` and `env` add to what it finds. ```ts title="stageboard.config.ts" import { defineConfig } from "stageboard" import { nextPreset } from "stageboard/next" export default defineConfig({ stories: ["src/**/*.stories.tsx"], preview: "./stageboard.preview.tsx", vite: () => nextPreset({ root: __dirname }), }) ``` See [Next.js](/guides/nextjs). ## stageboard/node The CLI's building blocks, for scripts and custom tooling. | Export | Signature | What it does | | --- | --- | --- | | `loadConfig` | `(from?: string, explicit?: string) => Promise` | Finds and loads `stageboard.config.ts`. | | `findConfigFile` | `(from?: string) => string \| undefined` | Searches upward for a config file. | | `buildIndex` | `(config: ResolvedConfig) => Promise` | Lists every story, as `stageboard list` does. | | `shoot` | `(config: ResolvedConfig, options: ShotOptions) => Promise` | Screenshots a story, as `stageboard shot` does. | | `matchStory` | `(index: IndexedStory[], query: string) => IndexedStory` | Resolves an id, `file#Export` or search term to one story. | | `createStageboardServer` | `(config: ResolvedConfig, options?: ServerOptions) => Promise` | Starts the stageboard server, as `stageboard dev` does. | | `findRunningServer` | `(config: ResolvedConfig) => Promise` | URL of a running `stageboard dev` for this project. | | `scanVariants`, `writeVariantsModule` | `(config: ResolvedConfig) => Promise<...>` | Find `*.variant.tsx` files and write the registry, as `stageboard variants` does. | It also exports `stageboardPlugin`, `Bridge`, `buildPrompt` and `createProvider`, which the server uses internally. ```ts title="scripts/shot-all.ts" import { buildIndex, loadConfig, shoot } from "stageboard/node" const config = await loadConfig() for (const story of await buildIndex(config)) { if (story.hasPage) console.log((await shoot(config, { story: story.id, width: 1440 })).file) } ```

{title}

{subtitle}

{cta}