Building Views
Create custom React interfaces with typed RPC to your backend
Views are custom React interfaces for your tools and workflows. Unlike traditional web apps, thereβs no REST API layer, your frontend calls backend tools directly via typed RPC.
Stack
- React 19 - UI library
- Tailwind v4 - Utility-first CSS (configured in
tailwind.config.js) - TanStack Router - Type-safe routing
- Vite - Fast bundler with hot reload
All configured out of the box in /view .
Calling Tools from React
The RPC client ( src/lib/rpc.ts ) connects your UI to backend tools with full type safety:
import { client } from "../lib/rpc";
import { useState } from "react";
function CustomerForm() {
const [status, setStatus] = useState("");
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const result = await client.tools.CUSTOMER_CREATE({
name: formData.get("name") as string,
email: formData.get("email") as string,
city: formData.get("city") as string,
state: formData.get("state") as string,
});
setStatus(`Customer created with ID: ${result.id}`);
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<input name="city" placeholder="City" required />
<input name="state" placeholder="State" maxLength={2} required />
<button type="submit">Create Customer</button>
{status && <p>{status}</p>}
</form>
);
}
Key benefits:
- Type safety - TypeScript knows all tool signatures
- No API layer - Direct function calls
- Auto-complete - IDE suggests available tools
Run npm run gen:self after adding tools to update types.
Adding Routes
TanStack Router provides type-safe routing. To add a new page:
- Create
/view/src/routes/my-page.tsx:
import { createRoute } from "@tanstack/react-router";
import { rootRoute } from "../main";
export const myPageRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/my-page",
component: MyPage,
});
function MyPage() {
return <div>My Page Content</div>;
}
- Register in
/view/src/main.tsx:
import { myPageRoute } from "./routes/my-page";
const routeTree = rootRoute.addChildren([
indexRoute,
myPageRoute, // Add your route
]);
- Navigate using
Link:
import { Link } from "@tanstack/react-router";
<Link to="/my-page">Go to My Page</Link>
Styling with Tailwind
Tailwind v4 is configured by default. Use utility classes:
<div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-md">
<h1 className="text-2xl font-bold text-gray-900">Title</h1>
<p className="text-gray-600">Description text</p>
<button className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
Click me
</button>
</div>
Use src/components/ui/ for reusable components (buttons, forms, etc.). You can integrate any React design system or component library as needed.
Theme Tokens
Views automatically inherit your workspace theme through CSS custom properties. Instead of hardcoding colors, use theme tokens for consistent branding:
Key tokens:
--background /* Page background */
--foreground /* Primary text color */
--card /* Card/panel backgrounds */
--primary /* Primary brand color (CTAs) */
--destructive /* Delete/error states */
--success /* Success/confirmation states */
--warning /* Warning/caution states */
--border /* Borders and dividers */
--radius /* Border radius */
Usage example:
<button
className="px-4 py-2 rounded-[var(--radius)]"
style={{
backgroundColor: 'var(--primary)',
color: 'var(--primary-foreground)'
}}
>
Primary Action
</button>
<div
className="p-4 border"
style={{
backgroundColor: 'var(--card)',
borderColor: 'var(--border)',
borderRadius: 'var(--radius)'
}}
>
Card content
</div>
Semantic tokens for states:
{/* Success state */}
<span style={{
backgroundColor: 'var(--success)',
color: 'var(--success-foreground)'
}}>
Active
</span>
{/* Warning state */}
<span style={{
backgroundColor: 'var(--warning)',
color: 'var(--warning-foreground)'
}}>
Pending
</span>
{/* Destructive state */}
<button style={{
backgroundColor: 'var(--destructive)',
color: 'var(--destructive-foreground)'
}}>
Delete
</button>
Why use theme tokens?
- Views automatically match workspace branding
- Theme changes donβt require code updates
- Consistent design system across all views
- Semantic meaning (primary vs destructive) built in
Development Workflow
Hot reload for frontend:
npm run dev
Edit React components β instant browser update (no refresh needed).
Backend changes: Edit tools/workflows β Worker restarts β refresh page to see changes.
Type updates: After adding/modifying tools:
npm run gen:self
Your client gets updated with new methods and types.
TypeScript prevents errors: If you call a tool that doesnβt exist, the compiler catches it before runtime.
Common Patterns
Loading states:
const [loading, setLoading] = useState(false);
const handleAction = async () => {
setLoading(true);
try {
await client.tools.MY_TOOL(data);
} finally {
setLoading(false);
}
};
Error handling:
try {
await client.tools.MY_TOOL(data);
} catch (error) {
console.error("Tool failed:", error);
setError(error.message);
}
Optimistic updates:
// Update UI immediately
setItems([...items, newItem]);
// Sync to backend
client.tools.ITEM_CREATE(newItem)
.catch(() => {
// Revert if failed
setItems(items);
});
State Management
For simple apps, Reactβs built-in useState and useEffect are sufficient. For complex state needs, you can add:
- React Context - Built-in, good for simple global state
- Zustand - Lightweight state management library
- React Query - For server state and caching
Since the RPC client returns promises, you can use React Query for automatic caching and refetching:
import { useQuery } from "@tanstack/react-query";
const { data, isLoading } = useQuery({
queryKey: ["customers"],
queryFn: () => client.tools.CUSTOMERS_LIST(),
});
The template doesnβt include global state management by default. Add it only if your app needs it.
Remember: Views and tools share the same types. No API contracts, no integration code, just TypeScript.
Found an error or want to improve this page?
Edit this page