Agent skill

canvas

Stars 5
Forks 1

Install this agent skill to your Project

npx add-skill https://github.com/parkerhancock/browser-canvas/tree/main/vanilla/skills/canvas

SKILL.md

Vanilla Canvas Skill

Build interactive UIs with vanilla JavaScript, semantic HTML, and pure CSS. No frameworks. No build step.

Philosophy

  • Vanilla JS - Modern JavaScript (ESM, async/await, fetch), no TypeScript
  • Native HTML - Use <select>, <input type="date">, <dialog>, <details> over JS equivalents
  • Semantic HTML - Structure with <main>, <nav>, <header>, <footer>, <article>, <section>
  • Pure CSS - Variables, modern features, meaningful class names. No Tailwind.
  • No build step - Files served directly. Import maps for libraries.

Creating a Canvas

Write an index.html file to .claude/artifacts/<canvas-id>/:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/base.css">
  <style>
    /* Canvas-specific styles using CSS variables */
    .dashboard {
      display: grid;
      gap: var(--space-4);
      padding: var(--space-6);
    }
  </style>
</head>
<body>
  <main class="dashboard">
    <article class="card">
      <header class="card-header">
        <h2>My Dashboard</h2>
      </header>
      <div class="card-body">
        <p>Content here</p>
      </div>
    </article>
  </main>

  <script type="module">
    // Your JavaScript here
  </script>
</body>
</html>

File Protocol

File Purpose
index.html Your canvas (HTML + CSS + JS)
_state.json Two-way state sync
_log.jsonl Events and errors
_screenshot.png Screenshot output

State Sync

javascript
// Read state
const state = await window.canvasState()

// Update state (writes to _state.json, notifies server)
await window.canvasState({ ...state, count: state.count + 1 })

// Listen for external state changes
window.addEventListener("canvas-state-change", (e) => {
  console.log("State changed:", e.detail)
})

Emitting Events

javascript
// Emit event (written to _log.jsonl)
window.canvasEmit("button-clicked", { id: "submit" })
window.canvasEmit("form-submitted", { name: "John", email: "john@example.com" })

Native HTML Patterns

Dialog (Modal)

html
<dialog id="confirm-dialog">
  <form method="dialog">
    <h3>Confirm Action</h3>
    <p>Are you sure you want to proceed?</p>
    <footer class="flex gap-2" style="margin-top: var(--space-4)">
      <button value="cancel" class="secondary">Cancel</button>
      <button value="confirm">Confirm</button>
    </footer>
  </form>
</dialog>

<script type="module">
  const dialog = document.getElementById("confirm-dialog")

  // Show dialog
  dialog.showModal()

  // Handle result
  dialog.addEventListener("close", () => {
    if (dialog.returnValue === "confirm") {
      // User confirmed
    }
  })
</script>

Accordion

html
<details>
  <summary>Click to expand</summary>
  <p>Hidden content revealed when expanded.</p>
</details>

<!-- Multiple items -->
<details name="accordion">
  <summary>Section 1</summary>
  <p>Content 1</p>
</details>
<details name="accordion">
  <summary>Section 2</summary>
  <p>Content 2</p>
</details>

Tabs

html
<div class="tabs" role="tablist">
  <button role="tab" aria-selected="true" data-tab="tab1">Tab 1</button>
  <button role="tab" data-tab="tab2">Tab 2</button>
</div>

<div id="tab1" role="tabpanel">Content 1</div>
<div id="tab2" role="tabpanel" hidden>Content 2</div>

<script type="module">
  document.querySelectorAll("[role=tab]").forEach(tab => {
    tab.addEventListener("click", () => {
      // Update aria-selected
      document.querySelectorAll("[role=tab]").forEach(t => {
        t.setAttribute("aria-selected", t === tab)
      })
      // Show/hide panels
      document.querySelectorAll("[role=tabpanel]").forEach(panel => {
        panel.hidden = panel.id !== tab.dataset.tab
      })
    })
  })
</script>

Date Picker

html
<label for="date">Select date</label>
<input type="date" id="date" min="2024-01-01" max="2025-12-31">

Select (Dropdown)

html
<label for="country">Country</label>
<select id="country">
  <option value="">Select a country</option>
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
  <option value="ca">Canada</option>
</select>

Using External Libraries

Use import maps for charting, mapping, or SQLite:

html
<script type="importmap">
{
  "imports": {
    "chart.js": "https://cdn.jsdelivr.net/npm/chart.js@4/+esm",
    "chart.js/auto": "https://cdn.jsdelivr.net/npm/chart.js@4/auto/+esm"
  }
}
</script>

<canvas id="chart"></canvas>

<script type="module">
  import Chart from "chart.js/auto"

  new Chart(document.getElementById("chart"), {
    type: "bar",
    data: {
      labels: ["Jan", "Feb", "Mar"],
      datasets: [{
        label: "Sales",
        data: [100, 150, 200]
      }]
    }
  })
</script>

CSS Variables (from base.css)

css
/* Colors */
--color-bg, --color-fg, --color-muted, --color-primary
--color-border, --color-input, --color-danger, --color-success

/* Spacing */
--space-1 (0.25rem), --space-2 (0.5rem), --space-3 (0.75rem)
--space-4 (1rem), --space-6 (1.5rem), --space-8 (2rem)

/* Radii */
--radius-sm (0.25rem), --radius (0.375rem), --radius-lg (0.5rem)

/* Shadows */
--shadow-sm, --shadow, --shadow-lg

/* Typography */
--font-sans, --font-mono
--text-xs, --text-sm, --text-base, --text-lg, --text-xl, --text-2xl

Utility Classes (from base.css)

css
.container    /* max-width centered container */
.card         /* bordered card with shadow */
.card-header  /* card header with border */
.card-body    /* card content area */
.card-footer  /* card footer with background */

.flex, .flex-col, .items-center, .justify-between
.gap-2, .gap-4
.text-sm, .text-muted, .font-medium, .font-bold
.sr-only      /* screen reader only */

Button Variants

html
<button>Primary</button>
<button class="secondary">Secondary</button>
<button class="danger">Danger</button>
<button class="ghost">Ghost</button>

Form Example

html
<form id="signup-form">
  <div style="display: grid; gap: var(--space-4);">
    <div>
      <label for="name">Name</label>
      <input type="text" id="name" required>
    </div>
    <div>
      <label for="email">Email</label>
      <input type="email" id="email" required>
    </div>
    <div>
      <label for="plan">Plan</label>
      <select id="plan" required>
        <option value="">Select a plan</option>
        <option value="free">Free</option>
        <option value="pro">Pro ($9/mo)</option>
      </select>
    </div>
    <div>
      <label for="start">Start Date</label>
      <input type="date" id="start">
    </div>
    <button type="submit">Sign Up</button>
  </div>
</form>

<script type="module">
  document.getElementById("signup-form").addEventListener("submit", (e) => {
    e.preventDefault()
    const data = Object.fromEntries(new FormData(e.target))
    window.canvasEmit("signup", data)
  })
</script>

Data Table

html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody id="users-table">
    <!-- Rows inserted by JS -->
  </tbody>
</table>

<script type="module">
  const users = [
    { name: "Alice", email: "alice@example.com", status: "Active" },
    { name: "Bob", email: "bob@example.com", status: "Pending" }
  ]

  document.getElementById("users-table").innerHTML = users.map(u => `
    <tr>
      <td>${u.name}</td>
      <td>${u.email}</td>
      <td>${u.status}</td>
    </tr>
  `).join("")
</script>

CanvasClient API (for operations)

javascript
import { CanvasClient } from "./vanilla/client.js"

const client = await CanvasClient.fromServerJson()
await client.screenshot("my-app")     // Take screenshot
await client.close("my-app")          // Close canvas
await client.getState("my-app")       // Read state
await client.setState("my-app", {...}) // Write state
await client.list()                   // List all canvases

Starting the Server

bash
cd tools/browser-canvas/vanilla
bun server.js

Server runs on port 3210 by default. Open http://localhost:3210 in browser.

Expand your agent's capabilities with these related and highly-rated skills.

parkerhancock/browser-canvas

canvas

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".

5 1
Explore
parkerhancock/ip_tools

ip_research

IP data research tools for patents, trademarks, and applications. Use when: - Looking up patents by number (US, EP, WO, JP, etc.) - Searching patent databases by keyword, assignee, inventor, or classification - Getting patent family, citation, or legal status information - Checking USPTO application status, file wrapper, or PTAB proceedings - Downloading bulk USPTO data products - Finding patent assignments or ownership history - Fetching USPTO publication full-text data

8 1
Explore
parkerhancock/dev-terminal

dev-terminal

Terminal/PTY automation with persistent sessions. Use to run and interact with TUI applications, debug terminal apps, automate CLI workflows, or any terminal interaction. Trigger phrases include "run the TUI", "start the app", "debug the terminal", "interact with", "send keys", "what's on screen".

1 0
Explore
mattpocock/skills

edit-article

Edit and improve articles by restructuring sections, improving clarity, and tightening prose. Use when user wants to edit, revise, or improve an article draft.

111,310 9,758
Explore
mattpocock/skills

obsidian-vault

Search, create, and manage notes in the Obsidian vault with wikilinks and index notes. Use when user wants to find, create, or organize notes in Obsidian.

111,310 9,758
Explore
mattpocock/skills

handoff

Compact the current conversation into a handoff document for another agent to pick up.

111,310 9,758
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results