Custom UIs
Replace the default machine UI with your own React components.
Overview
Every machine gets a generated UI by default — action buttons, a state inspector, and a history view. For most use cases this is enough.
When you need a tailored interface — a custom dashboard, a 2D machine visualisation, or purpose-built controls — you can provide your own React components. Vintage will compile them and serve them in place of the default UI.
Folder structure
Place a ui/ folder alongside your machine file. The entry point must be
ui/index.tsx (or ui/index.ts):
my-machine/
├── index.ts ← machine class
└── ui/
└── index.tsx ← custom UI entry pointFor multi-machine setups using defineConfig, specify the ui path per
machine:
export default defineConfig({
counter: {
class: CounterMachine,
ui: "./ui/index.tsx",
},
})Writing a component
The entry file default-exports a single React component. It receives a machine
prop with the current state and action helpers:
import type { Machine } from "@vintage/core"
import type { CounterState } from "../index"
export default function CounterUi({
machine,
}: {
machine: Machine<CounterState>
}) {
return (
<div>
<p>Count: {machine.state.count}</p>
<button onClick={() => machine.invoke.execute("increment")}>+</button>
<button onClick={() => machine.invoke.execute("decrement")}>-</button>
</div>
)
}The machine prop provides:
| Property | Type | Description |
|---|---|---|
machine.state | S | Current machine state, typed from your state interface |
machine.invoke.execute(name, payload?) | Promise<unknown> | Call a machine action by name |
machine.invoke.isLoading | boolean | True while an action is in flight |
machine.restart.execute() | Promise<void> | Restart the machine |
State updates arrive in real time over WebSocket — no polling needed.
Multiple views
The entry file can export a default component or a named map of views. Named views appear as tabs in the UI:
// ui/index.tsx
export default {
dashboard: DashboardView,
controls: ControlsView,
}Each view receives the same machine prop.
React and dependencies
React is provided globally by the base frontend bundle. Do not import or
bundle React yourself — it is resolved from window.React at runtime.
// ✓ correct — JSX works without importing React
export default function MyUi({ machine }) {
return <div>{machine.state.value}</div>
}
// ✗ incorrect — do not do this
import React from "react"Other dependencies are bundled normally. Keep them light — the UI bundle is compiled on the fly when the server starts.