Quick Start
By the end of this guide you’ll have a chart with one vessel, driven by a running Maelstrom engine that interprets a natural-language intent and positions the component on screen. The visible result is deliberately small: a single greeting card appears where the engine places it, proving that the client, connector, engine, and chart can complete one full turn.
Time: ~5 minutes | Framework: React
-
Install packages
Section titled “Install packages”Terminal window npm install @maelstrom-co/reactTerminal window bun add @maelstrom-co/reactTerminal window pnpm add @maelstrom-co/react@maelstrom-co/reactre-exports the vessel definition helper and connector classes (WebSocketConnector,HttpConnector), so you do not need to install lower-level Maelstrom packages separately for React apps. -
Create a component
Section titled “Create a component”First, create the React component that will render inside the vessel:
src/components/Greeting.tsx export function Greeting({ message }: { message: string }) {return (<div className="rounded-lg border p-4">{message}</div>);}This is just a regular React component. Maelstrom doesn’t impose any prop conventions — you define what the component receives via the vessel’s variant
props. -
Define a Vessel
Section titled “Define a Vessel”A vessel is a state machine: variants are its states, actions are transitions between them, and the LLM uses the
descriptionfields to understand what the vessel does and how to position it.minSizeis in chart cells. The engine enforces it for grid layouts. Tiling layouts derive each Vessel’s size from the split tree and ignoreminSize.src/vessels/greeting.tsx import { defineVessel, vessel } from '@maelstrom-co/react';import { Greeting } from '../components/Greeting';export const greetingVessel = vessel(defineVessel({id: 'greeting',name: 'Greeting',description: 'Displays a greeting message to the user.',defaultVariant: 'compact',variants: {compact: {description: 'Short one-line greeting',props: { message: 'Hello!' },minSize: { width: 2, height: 1 },actions: [],},},}),Greeting,);defineVessel(...)declares the framework-agnostic state machine;vessel(...)pairs that definition with a React component. -
Wire the Provider
Section titled “Wire the Provider”Create a shared module that exports the Maelstrom instance, then wrap your app with
m.Provider:src/maelstrom.ts import { createMaelstrom } from '@maelstrom-co/react';import { greetingVessel } from './vessels/greeting';export const m = createMaelstrom({vessels: [greetingVessel],});src/App.tsx import { WebSocketConnector } from '@maelstrom-co/react';import { m } from './maelstrom';const connector = new WebSocketConnector({url: 'ws://localhost:3001/ws',});export function App() {return (<m.Provider connector={connector}><m.Chart style={{ height: '100vh' }} /></m.Provider>);}m.Chartrenders the grid canvas. The engine emits layout directives that the client applies as CSS positioning — you don’t manage positions manually. -
Submit an intent
Section titled “Submit an intent”Add a simple input that calls
processIntent. TheuseMaelstromhook exposes connection state so you can give the user feedback while the engine is thinking:src/IntentBar.tsx import { useState } from 'react';import { m } from './maelstrom';export function IntentBar() {const { processIntent, connectionStatus, isProcessing } = m.useMaelstrom();const [value, setValue] = useState('');return (<formonSubmit={(e) => {e.preventDefault();if (!value.trim()) return;void processIntent(value.trim());setValue('');}}><inputvalue={value}onChange={(e) => setValue(e.target.value)}placeholder="Tell the engine what to show…"disabled={connectionStatus !== 'connected' || isProcessing}/><button type="submit" disabled={connectionStatus !== 'connected' || isProcessing}>{isProcessing ? 'Thinking…' : 'Send'}</button></form>);}Add
<IntentBar />below<m.Chart />in yourApp.tsx. -
What you should see
Section titled “What you should see”Type something like
show me a greetingand submit. The engine processes the intent and the greeting vessel appears on the chart at the grid position the engine chose. You should see one small card containing"Hello!"inside the chart area; the exact position may vary because the engine chooses the layout.The engine response includes layout directives — the client applies CSS positioning to place the vessel without you writing any placement code.
If the vessel doesn’t appear, check:
- The WebSocket engine is running (clone the demo repo and run
nx dev demo) - Your
.envhas a validOPENAI_API_KEY - The browser console for WebSocket connection errors
- The WebSocket engine is running (clone the demo repo and run
Next steps
Section titled “Next steps”- Core Concepts — vessels, charts, the engine, and the protocol in depth
- Installation — full setup matrix for all LLM providers and package managers
- Build Your First Dashboard — multi-vessel app with real actions
- React guides — variants, actions, lazy loading, error handling
- Client guides — custom connectors, programmatic layout
- API Reference — full TypeDoc for all packages