Claude
Skills
Sign in
Back

canvas

Included with Lifetime
$97 forever

Render interactive UIs in a browser window using file operations. Supports two modes: React (App.jsx) for rapid prototyping with pre-built components, and Vanilla (index.html) for standards-based, portable artifacts. 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".

Design

What this skill does


# Browser Canvas

## Choosing a Mode

The server auto-detects the mode based on filename:

| File | Mode | Best For |
|------|------|----------|
| `App.jsx` | React | Rapid prototyping, complex forms, dashboards with charts |
| `index.html` | Vanilla | Portable artifacts, standards-based code, durability |

### React Mode (App.jsx)
- Pre-bundled shadcn/ui components (Button, Card, Dialog, etc.)
- Tailwind CSS styling
- useState/useEffect reactivity
- Recharts for data visualization
- Hot-reload preserves state

### Vanilla Mode (index.html)
- Standards-based HTML/CSS/JS
- Native HTML elements (`<dialog>`, `<details>`, `<select>`, `<input type="date">`)
- Web components for composition
- CSS variables for styling
- Import maps for libraries (Chart.js, etc.)
- No build step - files served directly
- Portable - works without the server

## Setup

Start the server once per session:

```bash
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/`:

```jsx
// 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.

## Creating a Vanilla Canvas

Write an `index.html` file to a new folder:

```html
<!-- Write to: .claude/artifacts/my-app/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My App</title>
  <link rel="stylesheet" href="/base.css">
  <style>
    /* Canvas-specific styles */
    .container { padding: var(--space-6); max-width: 32rem; margin: 0 auto; }
  </style>
</head>
<body>
  <main class="container">
    <article class="card">
      <header class="card-header"><h2>Hello World</h2></header>
      <div class="card-body">
        <p>This renders in the browser!</p>
        <button id="action">Click Me</button>
      </div>
    </article>
  </main>

  <script type="module">
    document.getElementById('action').onclick = () => {
      window.canvasEmit('clicked', { timestamp: Date.now() })
    }
  </script>
</body>
</html>
```

### Vanilla CSS Variables (from base.css)

```css
--color-bg, --color-fg, --color-muted, --color-primary
--color-border, --color-input, --color-danger, --color-success
--space-1 (0.25rem) through --space-8 (2rem)
--radius-sm, --radius, --radius-lg
--shadow-sm, --shadow, --shadow-lg
```

### Vanilla Utility Classes

```css
.card, .card-header, .card-body, .card-footer
.flex, .flex-col, .items-center, .justify-between, .gap-2, .gap-4
.text-sm, .text-muted, .font-medium, .font-bold
button, button.secondary, button.danger, button.ghost
```

### Using Libraries in Vanilla Mode

Use import maps for external libraries:

```html
<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>

<script type="module">
  import Chart from 'chart.js'
  import { marked } from 'marked'
  // Use the libraries
</script>
```

### Web Components in Vanilla Mode

Define reusable components inline:

```html
<script>
  class StatCard extends HTMLElement {
    connectedCallback() {
      const value = this.getAttribute('value')
      const label = this.getAttribute('label')
      this.innerHTML = `
        <article class="card" style="padding: var(--space-4);">
          <div style="font-size: var(--text-2xl); font-weight: 700; color: var(--color-primary);">${value}</div>
          <div style="font-size: var(--text-sm); color: var(--color-muted);">${label}</div>
        </article>
      `
    }
  }
  customElements.define('stat-card', StatCard)
</script>

<!-- Use it -->
<stat-card value="$1,234" label="Revenue"></stat-card>
```

## Updating a Canvas

Edit the `App.jsx` or `index.html` file using the Edit tool. The browser hot-reloads automatically.

- **React mode:** Hot-reload preserves component state
- **Vanilla mode:** Full page refresh on change

## 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:

```bash
# 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:

```typescript
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:

```bash
bun
Files: 12
Size: 107.6 KB
Complexity: 58/100
Category: Design

Related in Design