Render React UIs in the browser using file operations
npx claudepluginhub parkerhancock/browser-canvasCreate interactive React UIs by writing files. Use for forms, charts, dashboards, and visual interfaces.
Render interactive UIs in the browser using only file operations
Quick Start • Features • React • Vanilla • Docs
Browser Canvas lets Claude Code create interactive UIs by writing files. No API calls, no special protocols—just write a file and watch it render.
Two modes, same workflow:
| Mode | File | Stack |
|---|---|---|
| React | App.jsx | shadcn/ui + Tailwind + React hooks |
| Vanilla | index.html | Pure HTML/CSS/JS, CSS variables |
Write App.jsx or index.html → Browser opens → Edit file → Hot reload → Read _log.jsonl
# In Claude Code
/plugin install parkerhancock/browser-canvas
Restart Claude Code after installation.
Requires Bun runtime.
git clone https://github.com/parkerhancock/browser-canvas.git
cd browser-canvas
bun install
./server.sh
The server watches .claude/artifacts/ in your current directory. When Claude Code writes an App.jsx file there, a browser window opens automatically.
Once the server is running, Claude Code creates UIs by writing files. The server auto-detects the mode based on filename:
.claude/artifacts/my-app/App.jsx → React mode (shadcn/ui, Tailwind)
.claude/artifacts/my-app/index.html → Vanilla mode (pure HTML/CSS/JS)
Both modes share the same file protocol (_state.json, _log.jsonl) and API.
| Feature | How It Works |
|---|---|
| Dual Mode | React (App.jsx) or Vanilla (index.html) — auto-detected |
| Hot Reload | Edit files → browser updates instantly |
| Unified Log | Events, errors, validation → _log.jsonl (grep-friendly) |
| Two-Way State | Agent writes _state.json ↔ canvas reads/writes |
| TypeScript API | CanvasClient for screenshots, close, state operations |
| Validation | ESLint, scope checking, Tailwind, accessibility (axe-core) |
| Auto-Feedback | PostToolUse hook injects validation errors after writes |
| Multi-Canvas | Toolbar dropdown switches between artifacts |
Write App.jsx for rapid prototyping with pre-bundled components:
function App() {
const [count, setCount] = useState(0);
return (
<Card className="w-80 mx-auto mt-8">
<CardContent className="pt-6 text-center">
<p className="text-4xl font-bold mb-4">{count}</p>
<Button onClick={() => setCount(c => c + 1)}>+</Button>
</CardContent>
</Card>
);
}
Everything is pre-bundled and available without imports:
React — useState, useEffect, useCallback, useMemo, useRef, useReducer, useCanvasState
shadcn/ui — Card, Button, Input, Table, Dialog, Tabs, Select, Checkbox, Switch, Badge, Alert, Tooltip, and more
Recharts — LineChart, BarChart, PieChart, AreaChart, ResponsiveContainer, XAxis, YAxis, Legend
Reusable Components — StatCard, DataChart, DataTable, ContactForm, ActivityFeed, ProgressList, MarkdownViewer
Utilities — cn() for classNames, format() from date-fns, Markdown, remarkGfm, Tailwind CSS
Write index.html for standards-based, portable artifacts:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My App</title>
<link rel="stylesheet" href="/base.css">
</head>
<body>
<main class="container">
<article class="card">
<header class="card-header"><h2>Counter</h2></header>
<div class="card-body">
<p class="count" id="count">0</p>
<button id="increment">+</button>
</div>
</article>
</main>
<script type="module">
let count = 0
document.getElementById('increment').onclick = () => {
count++
document.getElementById('count').textContent = count
window.canvasEmit('incremented', { count })
}
</script>
</body>
</html>
/base.css provides theming (--color-primary, --space-4, etc.)<dialog>, <details>, <select>, <input type="date"><script type="importmap">
{
"imports": {
"chart.js": "https://cdn.jsdelivr.net/npm/chart.js@4/auto/+esm",
"marked": "https://cdn.jsdelivr.net/npm/marked@15/+esm"
}
}
</script>