Vintage Machine Works

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 point

For 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:

PropertyTypeDescription
machine.stateSCurrent machine state, typed from your state interface
machine.invoke.execute(name, payload?)Promise<unknown>Call a machine action by name
machine.invoke.isLoadingbooleanTrue 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.

On this page