| name | canvas |
| description | Render interactive React UIs in a browser window using file operations. Use when users ask to: show forms, render charts/graphs, create dashboards, display data tables, build visual interfaces, or show any UI component. Trigger phrases: "show me", "render", "display", "create a form/chart/table/dashboard". |
Browser Canvas
Setup
Start the server once per session:
cd /path/to/browser-canvas && ./server.sh &
Wait for "Ready on http://localhost:9847" before proceeding.
Design First
Before creating a canvas, consider the aesthetic direction:
- Check for brand guidelines - Look for brand assets, style guides, or design tokens in the project (e.g.,
brand/,design/,.claude/canvas/) - Read
references/frontend-design.mdfor guidance on:- Typography choices (avoid generic fonts like Inter/Arial)
- Color palettes (commit to a cohesive theme)
- Motion and animations
- Spatial composition
Use the project's .claude/canvas/styles.css to implement custom fonts, colors, and effects.
Creating a Canvas
Write an App.jsx file to a new folder in .claude/artifacts/:
// Write to: .claude/artifacts/my-app/App.jsx
function App() {
return (
<Card className="w-96 mx-auto mt-8">
<CardHeader>
<CardTitle>Hello World</CardTitle>
</CardHeader>
<CardContent>
<p>This renders in the browser!</p>
</CardContent>
</Card>
);
}
The server auto-detects the new folder and opens a browser tab.
Updating a Canvas
Edit the App.jsx file using the Edit tool. The browser hot-reloads automatically.
Automatic Validation Feedback
When you write or edit an App.jsx file, validation errors are automatically injected into your next response. You don't need to manually check _log.jsonl for validation issues—they'll appear as context after each write.
Validation checks: ESLint (undefined variables, syntax), scope (missing components), Tailwind (invalid classes), bundle size.
Reading the Log
All canvas activity is logged to _log.jsonl. Use grep to filter by type:
# View all log entries
cat .claude/artifacts/my-app/_log.jsonl
# View recent events (user interactions)
grep '"type":"event"' .claude/artifacts/my-app/_log.jsonl | tail -10
# View errors only
grep '"severity":"error"' .claude/artifacts/my-app/_log.jsonl | head -5
# View validation notices
grep '"type":"notice"' .claude/artifacts/my-app/_log.jsonl | tail -10
# View specific issue categories
grep '"category":"scope"' .claude/artifacts/my-app/_log.jsonl # Missing components
grep '"category":"lint"' .claude/artifacts/my-app/_log.jsonl # Visual issues
grep '"category":"runtime"' .claude/artifacts/my-app/_log.jsonl # Runtime crashes
Log Entry Types
| Type | Description | Example |
|---|---|---|
event |
User interactions | {"type":"event","event":"submit","data":{"name":"John"}} |
notice |
Validation issues | {"type":"notice","severity":"error","category":"scope","message":"'Foo' is not available"} |
render |
Render lifecycle | {"type":"render","status":"success","duration":42} |
screenshot |
Screenshot captures | {"type":"screenshot","path":"_screenshot.png"} |
Notice Categories
| Category | Source | Description |
|---|---|---|
runtime |
Browser | Component crashes, uncaught errors |
lint |
Browser | axe-core visual issues (contrast, etc.) |
eslint |
Server | Code quality issues |
scope |
Server | Missing components or hooks |
tailwind |
Server | Invalid Tailwind classes |
overflow |
Browser | Layout overflow detection |
image |
Browser | Broken images |
bundle |
Server | Bundle size warnings |
Severity Levels
error- Must fix (component won't render or looks broken)warning- Should fix (code smell or minor issue)info- Optional improvement
Canvas Operations (TypeScript API)
Use the CanvasClient for operations like screenshots and closing canvases:
import { CanvasClient } from "browser-canvas"
const client = await CanvasClient.fromServerJson()
// Take a screenshot
const { path } = await client.screenshot("my-app")
// Screenshot saved to: .claude/artifacts/my-app/_screenshot.png
// Close a canvas
await client.close("my-app")
// Get/set state (alternative to file-based)
const state = await client.getState("my-app")
await client.setState("my-app", { step: 2 })
// Get validation status
const status = await client.getStatus("my-app")
// { errorCount: 0, warningCount: 1, notices: [...] }
// List all canvases
const canvases = await client.list()
// Check server health
const healthy = await client.health()
Run this as a script:
bun run my-script.ts
Taking Screenshots
import { CanvasClient } from "browser-canvas"
const client = await CanvasClient.fromServerJson()
await client.screenshot("my-app")
Then read the image: .claude/artifacts/my-app/_screenshot.png
Closing a Canvas
import { CanvasClient } from "browser-canvas"
const client = await CanvasClient.fromServerJson()
await client.close("my-app")
Available Components
All components are pre-loaded and available without imports.
React Hooks
useState, useEffect, useCallback, useMemo, useRef, useReducer
useCanvasState # Two-way state sync with agent
shadcn/ui Components
Layout:
Card,CardHeader,CardTitle,CardDescription,CardContent,CardFooterDialog,DialogTrigger,DialogContent,DialogHeader,DialogTitle,DialogDescription,DialogFooterSheet,SheetTrigger,SheetContentTabs,TabsList,TabsTrigger,TabsContentAccordion,AccordionItem,AccordionTrigger,AccordionContent
Forms:
Button,Input,Textarea,LabelSelect,SelectTrigger,SelectValue,SelectContent,SelectItemCheckbox,RadioGroup,RadioGroupItemSwitch,Slider
Data Display:
Table,TableHeader,TableBody,TableRow,TableHead,TableCellBadge,Avatar,AvatarImage,AvatarFallbackProgress,Skeleton
Feedback:
Alert,AlertTitle,AlertDescriptionTooltip,TooltipTrigger,TooltipContent,TooltipProvider
Charts (Recharts)
LineChart, BarChart, PieChart, AreaChart, RadarChart
Line, Bar, Pie, Area, Radar
XAxis, YAxis, CartesianGrid, Tooltip, Legend
ResponsiveContainer, Cell
Icons (Lucide)
All lucide-react icons are available:
Check, X, Plus, Minus, ChevronRight, ChevronDown, ChevronUp, ChevronLeft,
Search, Settings, User, Mail, Phone, Calendar, Clock, Bell,
FileText, Folder, Download, Upload, Trash, Edit, Copy, Save,
Home, Menu, MoreHorizontal, MoreVertical, ExternalLink, Link,
Eye, EyeOff, Lock, Unlock, Star, Heart, ThumbsUp, ThumbsDown,
ArrowRight, ArrowLeft, ArrowUp, ArrowDown, RefreshCw, Loader2,
AlertCircle, AlertTriangle, Info, HelpCircle, CheckCircle, XCircle
Utilities
cn()- className helper (clsx + tailwind-merge)format()- date-fns format functionMarkdown- react-markdown component for rendering markdownremarkGfm- GitHub Flavored Markdown plugin (tables, strikethrough, etc.)
Emitting Events
Components send data back to Claude via window.canvasEmit():
<Button onClick={() => window.canvasEmit('clicked', { buttonId: 1 })}>
Click Me
</Button>
Events appear in _log.jsonl for Claude to read.
Event Format
{"ts":"2026-01-07T10:30:00Z","type":"event","event":"eventName","data":{"key":"value"}}
Filter events with: grep '"type":"event"' _log.jsonl | tail -10
Two-Way State Sync
For stateful artifacts that need bidirectional communication, use _state.json:
How State Works
- No state file?
useCanvasState()returns{}(empty object) - Agent writes first? Canvas sees the state immediately on load
- Canvas writes first? Creates
_state.jsonfor agent to read - Either side updates? Changes sync automatically via WebSocket
Agent Sets State
Write state to control the canvas (can be done before or after canvas loads):
// Write to: .claude/artifacts/my-wizard/_state.json
{
"step": 2,
"message": "Please confirm your details",
"formData": { "name": "John" }
}
Canvas Reads/Writes State
Use the useCanvasState hook:
function App() {
const [state, setState] = useCanvasState();
return (
<Card className="w-96 mx-auto mt-8">
<CardHeader>
<CardTitle>Step {state.step || 1}</CardTitle>
</CardHeader>
<CardContent>
<p>{state.message || "Welcome!"}</p>
<Input
value={state.formData?.name || ""}
onChange={(e) => setState({
...state,
formData: { ...state.formData, name: e.target.value }
})}
/>
</CardContent>
<CardFooter>
<Button onClick={() => setState({ ...state, confirmed: true })}>
Confirm
</Button>
</CardFooter>
</Card>
);
}
Agent Reads Updated State
# Read: .claude/artifacts/my-wizard/_state.json
{"step":2,"message":"...","formData":{"name":"John"},"confirmed":true}
State vs Events
_state.json |
_log.jsonl events |
|---|---|
| Current snapshot | Append-only log |
| Two-way sync | One-way (canvas → agent) |
| "What's true now" | "What happened" |
| Good for: forms, wizards, settings | Good for: clicks, submissions, audit trail |
Use both together: state for current values, events for action history.
Reusable Components
Pre-built components are auto-loaded and available in your App.jsx. Use them with props for customization.
ContactForm
Validated form with customizable fields:
function App() {
return (
<ContactForm
title="Get in Touch"
description="We'll respond within 24 hours"
fields={[
{ name: "name", label: "Name", required: true },
{ name: "email", type: "email", label: "Email", required: true },
{ name: "company", label: "Company" },
{ name: "message", type: "textarea", label: "Message", required: true }
]}
submitLabel="Send Message"
onSubmit={(data) => window.canvasEmit("contact", data)}
/>
)
}
Props: title, description, fields, onSubmit, submitLabel, successMessage, showReset, className
DataChart
Flexible charting (line, bar, area, pie):
function App() {
const data = [
{ month: "Jan", sales: 4000, revenue: 2400 },
{ month: "Feb", sales: 3000, revenue: 1398 },
{ month: "Mar", sales: 2000, revenue: 9800 },
]
return (
<DataChart
type="bar"
data={data}
xKey="month"
series={[
{ dataKey: "sales", color: "#8884d8", label: "Sales" },
{ dataKey: "revenue", color: "#82ca9d", label: "Revenue" }
]}
title="Monthly Performance"
height={300}
/>
)
}
Props: type (line/bar/area/pie), data, xKey, series, title, description, height, onClick, showLegend, showGrid, formatValue
DataTable
Searchable, selectable data table:
function App() {
const users = [
{ id: 1, name: "Alice", email: "alice@example.com", status: "active" },
{ id: 2, name: "Bob", email: "bob@example.com", status: "pending" },
]
return (
<DataTable
data={users}
columns={[
{ key: "name", label: "Name" },
{ key: "email", label: "Email" },
{ key: "status", label: "Status", render: (v) => <Badge>{v}</Badge> }
]}
title="Team Members"
searchable
selectable
onRowClick={(row) => window.canvasEmit("user-click", row)}
actions={[
{ icon: Pencil, onClick: (row) => window.canvasEmit("edit", row) },
{ icon: Trash2, variant: "ghost", onClick: (row) => window.canvasEmit("delete", row) }
]}
/>
)
}
Props: data, columns, title, description, searchable, selectable, onRowClick, onSelectionChange, actions, emptyMessage
StatCard
Single statistic display:
<StatCard
title="Total Revenue"
value="$45,231"
change="+20.1%"
trend="up"
icon={DollarSign}
/>
Props: title, value, change, trend (up/down), icon, onClick, description
ActivityFeed
Recent activity list:
<ActivityFeed
items={[
{ id: 1, user: "Alice", avatar: "AJ", action: "completed order #1234", time: "2 min ago" },
{ id: 2, user: "Bob", avatar: "BS", action: "signed up", time: "15 min ago" },
]}
title="Recent Activity"
onItemClick={(item) => window.canvasEmit("activity-click", item)}
/>
Props: items, title, description, onItemClick, emptyMessage, maxItems, showViewAll, onViewAll
ProgressList
Multiple progress bars:
<ProgressList
title="Goals Progress"
items={[
{ label: "Revenue Target", current: 45231, max: 50000 },
{ label: "New Customers", value: 90 },
{ label: "Orders", current: 1234, max: 1500, format: (c, m) => `${c} of ${m}` },
]}
/>
Props: items, title, description, showPercentage
MarkdownViewer
Beautiful markdown document renderer with refined typography:
const markdown = `
# Document Title
This is a paragraph with **bold** and *italic* text.
## Section
- List item one
- List item two
\`\`\`javascript
const greeting = "Hello, world!"
\`\`\`
`
function App() {
return (
<MarkdownViewer
content={markdown}
title="Documentation"
variant="default"
showTableOfContents={true}
/>
)
}
Props: content (markdown string), title, variant (default/compact/wide), showTableOfContents, className
Features: GFM tables, code blocks, blockquotes, lists, links, images, heading anchors
Custom Project Components
Create project-specific components in .claude/canvas/components/:
// .claude/canvas/components/MyWidget.jsx
/**
* MyWidget - Custom component for this project
* @prop {string} title - Widget title
*/
function MyWidget({ title }) {
return (
<Card>
<CardHeader><CardTitle>{title}</CardTitle></CardHeader>
<CardContent>Custom content here</CardContent>
</Card>
)
}
Components are auto-loaded on server start. Project components override skill components with the same name.
Project Extensions
Add custom libraries, Tailwind plugins, or CSS by creating files in .claude/canvas/.
Adding Libraries
Install packages and export them for use in canvas:
bun add react-markdown
// .claude/canvas/scope.ts
import ReactMarkdown from "react-markdown"
export const extend = {
ReactMarkdown
}
Then use in App.jsx:
function App() {
return <ReactMarkdown># Hello</ReactMarkdown>
}
Adding Tailwind Plugins
// .claude/canvas/tailwind.config.js
export default {
plugins: [
require('@tailwindcss/typography'),
],
}
Custom CSS
/* .claude/canvas/styles.css */
.custom-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.prose pre {
background: #1e1e2e;
}
Extensions are detected at server startup and bundled automatically. Restart the server after adding extensions.
Reference Documentation
Read these references as needed:
| Reference | When to Read |
|---|---|
references/frontend-design.md |
Read first - Before creating any new canvas |
references/components.md |
Need specific shadcn/ui component props or variants |
references/charts.md |
Building charts with Recharts |
references/patterns.md |
Building forms, tables, wizards, or dashboards |
Canvas Directory Structure
Artifacts are stored in .claude/artifacts/ by default (version-controllable):
.claude/artifacts/
├── server.json # Server state (port, active canvases)
├── _server.log # Server logs
├── my-form/ # Canvas folder
│ ├── App.jsx # Component code (you write this)
│ ├── _log.jsonl # Unified log: events, notices, errors (server writes)
│ ├── _state.json # Two-way state (you + canvas write)
│ └── _screenshot.png # Screenshot output (API writes)
└── data-viz/
└── App.jsx
Browser Toolbar
The browser displays a toolbar at the top with:
- Claude Canvas branding
- Artifact dropdown to switch between canvases
- Connection status indicator
Use the dropdown to navigate between different artifacts without leaving the browser.
Tips
- Use Tailwind classes for styling - all utilities available
- Use
useCanvasStatefor two-way communication - Emit events for actions needing audit trail
- Check
_state.jsonfor current form values - Grep
_log.jsonlfor errors:grep '"severity":"error"' _log.jsonl - Take screenshots to verify visual output
- Edit incrementally - hot-reload preserves state
Reporting Issues
Report bugs or request features:
gh issue create --repo parkerhancock/browser-canvas --title "Bug: [description]"