Stackship Circuit template
A React 19 + Vite starting point for apps that Circuit builds and Stackship runs. Routing, light/dark theming, a typed API layer and the Stackship components are already wired, so an agent (or you) starts with product work instead of plumbing.
It follows the same contract as Circuit's frontend template — dev server on :5173, mock API
at /api, .eventsourced/session.yaml for the live preview — with the Stackship design system
on top.
Stack
- React 19 with the React Compiler enabled
- Vite 8 dev server, with a mock API served at
/api - Tailwind v4 — Stackship tokens defined as CSS variables in
src/index.css - Radix UI (unified
radix-uipackage) for accessible primitives - SWR for data fetching, react-router for routing, lucide-react for icons
- Poppins + JetBrains Mono bundled through Fontsource — no CDN calls, works air-gapped
Structure
src/
api/ # typed API layer — one SWR hook per endpoint
types.ts # response types
client.ts # fetch wrapper (all network calls go through here)
hooks.ts # useStats(), useResources(), … consumed by pages
components/
ui/ # Stackship primitives: Button, Card, Badge, ServiceIcon, ProgressBar, Skeleton
gallery/ # component-gallery demos, one primitive per file
dashboard/ # dashboard sections (StatGrid, ResourcesTable, SentinelCard…)
home/ # home page sections (Hero, EnvironmentCard, LoopSection, StackGrid)
Layout.tsx # header + footer, wraps every route
Logo.tsx # Stackship wordmark, light/dark aware
ThemeProvider.tsx / ThemeToggle.tsx
pages/ # one file per route, registered in App.tsx
mock/server.mjs # zero-dependency mock API (started by the dev server)
public/ # favicon and Stackship logos
Conventions (follow these when editing — applies to AI agents too)
- Colours and fonts come from tokens only. Use Tailwind classes like
bg-card,text-muted-foreground,border-border,bg-primary. Never hardcode hex values; both modes read from the variables at the top ofsrc/index.css. The one exception is the always-dark hero surface, which usesbg-nightand white text in both themes. - Brand utilities:
text-gradient(blue→green headline words),bg-brand(gradient CTAs and meters),glass,glow-primary/glow-accent(blurred backdrops on dark sections). Use the gradient sparingly — one highlight per screen reads as deliberate. - Resource types get their accent from
ServiceIcon(app,database,storage, …). Don't invent new hues for them. - Sentinel (the AI/ops assistant) uses the rose
sentinelcolour and nothing else does. - New route = one file in
src/pages/+ one line insrc/App.tsx(+ nav entry inLayout.tsx). - New data = type in
src/api/types.ts, route inmock/server.mjs, hook insrc/api/hooks.ts. Pages never callfetchdirectly. - Loading with
<Skeleton />, errors inline with<ErrorNote />and a retry — seesrc/pages/Dashboard.tsx. - Pages are compositions. A page owns layout and data fetching, then renders sections from
src/components/<page>/. If a page file grows past ~100 lines, extract sections. - Copy: sentence case for headings and buttons, no exclamation marks, no emoji. Calm and declarative — "You control where data lives", not "Unleash your data".
- Radix provides behaviour, Tailwind provides looks. Copy styles from the gallery
(
src/pages/Components.tsx) rather than inventing new patterns.
Commands
npm run dev # app on :5173, mock API proxied at /api
npm run build # type-check + production build
npm run lint # oxlint
Theming
Light and dark are two sets of CSS variables in src/index.css (:root and .dark). The
header toggle offers light, dark and system, and persists to localStorage. To retheme, change
the variable values — no component edits needed.
Swapping in a real API
Point server.proxy['/api'] in vite.config.ts at your backend and remove the mockApi()
plugin. Endpoint shapes stay in src/api/types.ts.