Claude Code Plugins

Community-maintained marketplace

Feedback

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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:

  1. Check for brand guidelines - Look for brand assets, style guides, or design tokens in the project (e.g., brand/, design/, .claude/canvas/)
  2. Read references/frontend-design.md for 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, CardFooter
  • Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter
  • Sheet, SheetTrigger, SheetContent
  • Tabs, TabsList, TabsTrigger, TabsContent
  • Accordion, AccordionItem, AccordionTrigger, AccordionContent

Forms:

  • Button, Input, Textarea, Label
  • Select, SelectTrigger, SelectValue, SelectContent, SelectItem
  • Checkbox, RadioGroup, RadioGroupItem
  • Switch, Slider

Data Display:

  • Table, TableHeader, TableBody, TableRow, TableHead, TableCell
  • Badge, Avatar, AvatarImage, AvatarFallback
  • Progress, Skeleton

Feedback:

  • Alert, AlertTitle, AlertDescription
  • Tooltip, 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 function
  • Markdown - react-markdown component for rendering markdown
  • remarkGfm - 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.json for 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

  1. Use Tailwind classes for styling - all utilities available
  2. Use useCanvasState for two-way communication
  3. Emit events for actions needing audit trail
  4. Check _state.json for current form values
  5. Grep _log.jsonl for errors: grep '"severity":"error"' _log.jsonl
  6. Take screenshots to verify visual output
  7. Edit incrementally - hot-reload preserves state

Reporting Issues

Report bugs or request features:

gh issue create --repo parkerhancock/browser-canvas --title "Bug: [description]"