Getting started
How the Petavue front end is put together, and the rules every screen follows.
The stack
| Part | What we use |
|---|---|
| Framework | React 19, built with Vite 7 |
| Routing | React Router 7 (src/router.jsx) |
| Data | TanStack Query for server state, zustand for client state |
| Styling | CSS custom properties for tokens, plain CSS files with BEM class names, Tailwind 4 for page layout |
| Components | @/ui (src/ui/components), plus the dropdown menu in @/components/shadcn/dropdown-menu |
| Icons | Phosphor (@phosphor-icons/react) |
| Motion | motion/react for enter and exit, CSS transitions for hover and state |
| Toasts | sonner, mounted once in the app layout |
@ is an alias for src/.
Where things live
src/
ui/
components/ the design system components, exported from @/ui
tokens/ tokens.css: colour, the single source of truth
colorography/ tokens.css: spacing, radius, borders, shadows
typography/ typography.css: font, sizes, line heights, text classes
components/
shadcn/ the dropdown menu (Base UI), themed in Petavue colours
pages/ one folder per area of the product
mocks/ sample data for the prototypeTokens and components are loaded once in src/main.jsx, so every page can use them without importing CSS.
Using a component
Import from @/ui. Never import from a component's own folder.
import { useState } from "react";
import { Button, TextInput } from "@/ui";
export function RenamePage({ page, onSave }) {
const [name, setName] = useState(page.name);
return (
<form className="rename-page" onSubmit={(e) => { e.preventDefault(); onSave(name); }}>
<TextInput label="Page name" value={name} onChange={(e) => setName(e.target.value)} />
<Button type="submit" variant="primary" label="Save" />
</form>
);
}Using tokens
Use the CSS variables. Never paste a hex value into new code.
.pipeline-card {
padding: var(--spacing-16);
border: var(--border-default) solid var(--color-grey-200);
border-radius: var(--radius-md);
color: var(--color-text-primary);
}In Tailwind, the grey and primary ramps exist as utilities (text-grey-500, bg-primary-50). For everything else, use the variable in an arbitrary value: text-[var(--color-text-secondary)].
Rules every screen follows
- Use the component, not a lookalike. If
@/uihas a Button, Dialog, Dropdown or Tooltip, use it. A<div>styled to look like a button is a bug. - Tokens only. Colour, spacing, radius and shadow come from tokens. If no token fits, raise it; do not add a one-off value.
- Product palette only. Blue for actions and focus, greys for everything structural, green, red and orange for status. Purple is reserved for components and the Creative agent family.
- Two text sizes for most UI: 14px and 12px. Bigger sizes are for page and section titles. Nothing below 12px.
- One primary action per area. Every other action is quieter.
- Plain copy. Short, specific, no exclamation marks, no taglines. See Writing.
- Class names say what a thing is. Library code uses BEM (
lib-card__title), never utility soup that hides meaning. Tailwind is fine for page layout. - Change a shared component only when the change should apply everywhere. For a one-off, style around it with a scoped class.
Working with an AI coding agent
Point the agent at /llms.txt before it writes UI. Every page here is also available as Markdown: use "Copy Markdown" at the top of any page, or open /llms.mdx/docs/<page>/content.md. The rules agents follow are in For AI & LLMs.