posthog/building-html-canvases
> Author a PostHog canvas with semantic HTML, CSS, and direct browser APIs — documents, articles, generative graphics, 2D canvas and WebGL experiences, and focused experiments where React components add no useful structure. Use after building-canvases has routed a canvas request to a plain-HTML/browser-API implementation. Covers the thin component wrapper the current runtime requires, styling and theming without Quill, drawing surfaces, and animation/cleanup patterns.
npx skills add https://github.com/PostHog/posthog --skill building-html-canvases
Some canvases are documents or graphics programs, not applications: a written report, a diagram,
a generative-art piece, a WebGL scene. For these, semantic HTML, CSS, and direct browser APIs are
the right tools — don't force Quill components or React state onto a static page.
Every canvas keeps src/canvas.tsx as its mounted React entry component (default export, no
props). Keep the React layer as a thin shell and write the
experience in HTML/CSS/browser APIs inside it:
<article>, headings, lists, tables,figures — with a <style> block for typography and layout. Write real, specific copy.
<canvas> element and drives it imperatively from auseEffect via a ref: get the 2D/WebGL context, run the setup and render loop there.
requestAnimationFrame loops, remove listeners, andrelease contexts, so theme switches and remounts don't leak or double-run.
can hand a chart's <canvas> to imperative code while React owns the chrome.
The import allowlist still applies (react, react-dom, @posthog/quill, recharts, lucide-react,
dayjs) — browser globals (document, CanvasRenderingContext2D, WebGLRenderingContext,
requestAnimationFrame, IntersectionObserver, Web Audio, etc.) need no import. Three.js and
other npm graphics libraries are not yet loadable; write against raw WebGL or 2D canvas until the
build pipeline's dependency admission ships.
<style> block (keyframes and complex selectors are fine)..dark class on the document root when the user's PostHog theme changes.Define your colors as CSS variables under :root { … } with overrides under html.dark { … },
or use theme token utilities (bg-background, text-foreground, border-border) — never a
light-only hardcoded color.
(getComputedStyle(document.documentElement).getPropertyValue("--primary")) or your own CSS
variables, and re-read on theme change if the scene is long-lived.
fetch()/XMLHttpRequest, no <script> tags, no dynamic import(), no remote assets —the sandbox blocks them. PostHog data comes only through the ph bridge (see the
querying-canvas-data skill), including ph.capture for interaction analytics.
ph.openExternal(url) (posthog.com origins only), from a userinteraction.
validating-and-publishing-canvases.Take posthog/building-html-canvases from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.