---
title: Gateway UI Components
description: Prebuilt React components (RunList, RunTree, RunEventLog, ApprovalPanel, LaunchButton, and more) for assembling a custom workflow UI in minutes, built on the gateway-react hooks.
---

`@smithers-orchestrator/gateway-ui` is a set of drop-in React components for
building a custom workflow UI. They are built on the
[`gateway-react`](/reference/gateway-react) hooks, so each one connects to the
Gateway by itself: give it a `runId` or a filter and it renders live data and
wires the actions. Reach for these instead of hand-wiring hooks when you want a
run dashboard fast.

Import them from the `smithers-orchestrator/gateway-ui` subpath, alongside
`createGatewayReactRoot` from `gateway-react`. Every component is styled with
inline styles (no CSS import), so it bundles cleanly through the Gateway's
bundler, and every component accepts `className` and `style` to override.

The inline styles are theme-following: every color is a
`var(--token, #lightFallback)` expression over the
[workflow UI style-guide tokens](/guides/workflow-ui-design), which the
Gateway's HTML shell injects into every `/workflows/<key>` page. Components
therefore render light or dark with the rest of the page, following the OS
`prefers-color-scheme` or an explicit `data-theme="dark|light"` on `<html>`
(which the shell sets from a `?theme=` query param). Outside a themed page the
fallbacks reproduce the light look. Tints derive via `color-mix` over the same
tokens, never hex-plus-alpha.

## Components

| Component | What it renders | Key props |
| --- | --- | --- |
| `RunList` | A live, selectable list of runs with a status pill per row. | `filter`, `activeRunId`, `onSelect`, `pollMs` |
| `RunTree` | The node tree for a run (initial snapshot plus live updates). | `runId`, `onSelectNode`, `activeNodeId` |
| `RunEventLog` | A scrolling, auto-following event log for a run. | `runId`, `maxEvents`, `follow` |
| `NodeOutputView` | The output of a single node, fetched on demand. | `runId`, `nodeId`, `iteration` |
| `ApprovalPanel` | The pending approval queue with Approve and Deny buttons. | `filter`, `pollMs`, `onError` |
| `LaunchButton` | A button that launches a workflow run. | `workflow`, `input`, `onLaunched` |
| `WorkflowPicker` | A select of the workflows registered on the Gateway. | `value`, `onChange`, `hasUiOnly` |
| `ConnectionBadge` | A live Gateway connection indicator. | none |
| `StatusPill` | A colored status badge for any run or node status string. | `status`, `label` |

`StatusPill`, `statusColor`, and the `theme` style tokens are also exported for
building your own rows that match the components.

## A complete UI

This is a full `.smithers/ui/<workflow>.tsx` bundle: a run picker, the node tree,
a live event log, a node-output pane, and the approval queue, wired together with
local state. Save it next to a workflow, declare it from that workflow with
`<UI entry="../ui/<workflow>.tsx" />`, and open it with
`bunx smithers-orchestrator ui RUN_ID` (or `bunx smithers-orchestrator ui --workflow <workflow>`).
To open the full local control surface instead of a single workflow UI, see
[`bunx smithers-orchestrator ui --app`](#the-full-local-smithers-ui) below.

```tsx
/** @jsxImportSource react */
import { useState } from "react";
import { createGatewayReactRoot } from "smithers-orchestrator/gateway-react";
import {
  ApprovalPanel,
  ConnectionBadge,
  LaunchButton,
  NodeOutputView,
  RunEventLog,
  RunList,
  RunTree,
  WorkflowPicker,
} from "smithers-orchestrator/gateway-ui";

const WORKFLOW = "implement";

function App() {
  const [runId, setRunId] = useState<string>();
  const [nodeId, setNodeId] = useState<string>();

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12, padding: 16 }}>
      <header style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <h1 style={{ margin: 0, fontSize: 18 }}>Implement</h1>
        <ConnectionBadge />
        <span style={{ flex: 1 }} />
        <LaunchButton workflow={WORKFLOW} onLaunched={setRunId} />
      </header>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12 }}>
        <RunList filter={{ workflow: WORKFLOW, limit: 20 }} activeRunId={runId} onSelect={setRunId} />
        <RunTree runId={runId} activeNodeId={nodeId} onSelectNode={(node) => setNodeId(node.id)} />
        <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
          <NodeOutputView runId={runId} nodeId={nodeId} />
          <RunEventLog runId={runId} style={{ height: 240 }} />
        </div>
      </div>

      <ApprovalPanel filter={{ workflow: WORKFLOW }} />
    </div>
  );
}

createGatewayReactRoot(<App />);
```

That is the whole UI. `RunList` polls for new runs, `RunTree` and `RunEventLog`
stream live as the run advances, `ApprovalPanel` lets you clear human gates, and
`LaunchButton` starts a fresh run. Mix these with raw
[`gateway-react`](/reference/gateway-react) hooks whenever you need something the
components do not cover.

## The full local Smithers UI

`bunx smithers-orchestrator ui --app` serves the full local control surface (`apps/smithers`)
instead of a single workflow's UI. It builds the app bundle on first use, serves
it same-origin with a local Gateway (reverse-proxying the Gateway's RPC,
WebSocket, and health paths), and opens it in your browser as a dashboard for
your runs, approvals, workspace files, and VCS state.

### Prerequisites

The app ships a local "concierge" chat that answers questions and backgrounds
workflows for you, so `--app` fails fast unless both of these hold:

- A chat credential. Set exactly one of `CEREBRAS_API_KEY` (recommended,
  fastest), `OPENAI_API_KEY`, `CODEX_ACCESS_TOKEN`, or `CODEX_REFRESH_TOKEN`.
  With none set, the command exits before it binds a port and prints which
  variable to set.
- `bun` on your `PATH`. The concierge runs on Bun, so the command checks for it
  up front and exits with an install hint if it is missing.

```bash
CEREBRAS_API_KEY=csk-... bunx smithers-orchestrator ui --app
```

### The concierge

The concierge is a small local chat backend the app talks to at `/api/chat`. Its
system prompt is seeded with the live catalog of workflows registered on your
Gateway, so it can background any of them for you, including `create-workflow`,
the meta-workflow that authors a brand-new workflow. It streams answers back
token by token, binds to loopback only, and needs no cloud account.

### Local workspace endpoints

Alongside the Gateway proxy, the `--app` server exposes a few loopback-only
endpoints the app uses to read and edit your working tree:

- `GET /api/files/tree`, `GET /api/files/read`, and `POST /api/files/write` for
  the file browser and editor.
- `/__smithers/vcs` for the local VCS (jj/git) status the app renders.
- `/__smithers/local-workspace` and `/__smithers/local-workspace/readiness` for
  workspace detection and readiness against the proxied Gateway.

These bind to `127.0.0.1` only and are scoped to the directory you launch from.

### Flags

| Flag | Default | What it does |
| --- | --- | --- |
| `--app` | `false` | Serve the full local UI instead of a single workflow run UI. |
| `--app-port <port>` | `7332` | Port to serve the full UI on. |
| `--rebuild` | `false` | Force a rebuild of the UI bundle before serving. |
| `--gateway <url>`, `-g` | `http://127.0.0.1:<port>` | Gateway base URL to proxy and read runs from. |
| `--port <port>` | `7331` | Gateway port when `--gateway` is not set. |
| `--no-open` | opens a browser | Print the URL instead of opening a browser. |
| `--no-autostart` | autostarts | Do not start a Gateway if none is reachable. |
| `--no-daemon` | daemon allowed | Force direct/embedded operation and never start a background Gateway daemon. |

Without `--app`, `bunx smithers-orchestrator ui [runId]` opens a single workflow run's custom UI
(the component gallery above) against the Gateway rather than the full surface.

## When to drop to hooks

The components own the common shapes. When you need a bespoke layout, an
embedded editor, or a custom card, read the same hooks the components use
(`useGatewayRuns`, `useGatewayRunTree`, `useGatewayRunEvents`,
`useGatewayApprovals`, `useGatewayActions`) directly. See
[Custom Workflow UIs](/guides/custom-workflow-ui) for the full hook walkthrough
and [Workflow UI (React)](/examples/workflow-ui-react) for a hand-wired example.
