/building-react-quill-canvases
Author the React + Quill implementation of a PostHog canvas: the single-component contract, the allowed imports, Quill (PostHog's design system) component and composition rules, theme-aware design tokens, loading skeletons, and the in-canvas date picker. Use after
$ npx -y skills add posthog/posthog --skill building-react-quill-canvases --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/building-react-quill-canvases
Context preview
The summary Claude sees to decide when to auto-load this skill.
Author the React + Quill implementation of a PostHog canvas: the single-component contract, the allowed imports, Quill (PostHog's design system) component and composition rules, theme-aware design tokens, loading skeletons, and the in-canvas date picker. Use after
SKILL.md
building-react-quill-canvases.SKILL.mdname: building-react-quill-canvases
description: >
Author the React + Quill implementation of a PostHog canvas: the single-component contract, the
allowed imports, Quill (PostHog's design system) component and composition rules, theme-aware
design tokens, loading skeletons, and the in-canvas date picker. Use after building-canvases has
routed a canvas request to a React implementation — dashboards, data boards, forms, tools, or any
canvas that should look native to PostHog.
Building React + Quill canvases
The whole application is one React/TSX file (`src/canvas.tsx` in the source project). It must `export default` a single React component that takes no props — the host mounts it. Do not import react-dom or call createRoot.
Start from the working scaffold in [references/starter-scaffold.md](references/starter-scaffold.md) on a first build: it already wires the date picker, theme tokens, per-card skeletons, and correct typed-node result reading. Keep that wiring; replace the sample metric and layout.
Imports
Import only from: `react`, `react-dom`, `react-dom/client`, `@posthog/quill`, `recharts`, `lucide-react`, `dayjs`. Anything else — including dynamic `import()`, `require()`, `fetch()`, `<script>` tags, or remote code — fails validation. Use `@posthog/quill` for UI, `recharts` for charts, `lucide-react` for icons, `dayjs` for dates.
Quill component rules
A PostHog data board must be built entirely from `@posthog/quill` components — never a native control or a styled `<div>` standing in for one:
- Dropdown/picker → `Select` (never a native `<select>`); button → `Button` (never `<button>`);
text field → `Input`/`Textarea`; checkbox → `Checkbox`; label → `Label`.
- Table → `Table` (`TableHeader` > `TableRow` > `TableHead`, then `TableBody` > `TableRow` > `TableCell`);
panel → `Card` (`CardHeader` + `CardTitle` + `CardContent`); pill → `Badge`; titles → `Heading`; body → `Text`.
- The only non-Quill tags allowed are plain layout `<div>`s and `recharts` elements.
- Quill is built on Base UI: compose compound parts (`Select` + `SelectTrigger`/`SelectContent`/`SelectItem`),
use controlled `value` + `onValueChange`, and swap a part's element with the `render` prop (e.g. `<PopoverTrigger render={<Button …/>} />`) instead of wrapping it.
- Quill components are already themed — never restyle one with Tailwind classes or inline `style`;
use their `variant`/`size` props. Put layout utilities (`flex`, `grid`, `gap-4`, `p-4`) on your own wrapper `<div>`s.
- Buttons: default to `variant="outline"`; `variant="primary"` for the one main action only.
Styling and theme
- Style with Tailwind utilities and Quill components; reserve inline `style` for genuinely dynamic
runtime values (fixed sizes use arbitrary-value utilities like `h-[280px]`).
- The canvas follows the user's PostHog theme; a `.dark` class on the document root flips at runtime.
Color only from the design-token utilities — surfaces `bg-background bg-card bg-muted bg-primary bg-success bg-warning bg-info bg-destructive`; text `text-foreground text-muted-foreground text-card-foreground`; borders `border-border`. Never a hardcoded hex or light-only color.
- Status tokens invert the usual convention: the bare token (`bg-success`) is a pale background fill
and `-foreground` (`text-success-foreground`) is the strong readable color. Colored text or icons always use the `-foreground` utility; a filled pill pairs `bg-success text-success-foreground`. Prefer the Quill `Badge` (`variant="success"`/`"destructive"`) for deltas so you don't hand-pick.
- `bg-secondary`, `text-secondary`, `bg-accent`, and `bg-popover` are not defined in the canvas — avoid them.
- recharts strokes/fills use token CSS variables (`stroke="var(--primary)"`, grid/axes in
`var(--border)`/`var(--muted-foreground)`).
- Write Unicode glyphs (curly quotes, ellipsis, arrows, emoji) as literal characters in JSX —
`\uXXXX` escapes render verbatim in JSX text.
Loading, error, and empty states
Every data point renders a skeleton in its own `Card` while loading or refreshing: `SkeletonText` (matching `lines` and text-size `className`) for text/number values, `Skeleton` for blocks/charts. Drive `isLoading` off the data calls and set it true again on refresh; never show a blank or a jumping layout.
A failed query and an empty result are different states — never let one render as the other. `.catch` on every `ph.query`/`ph.loadInsight` must set an error state that renders visibly (the message plus a Retry button wired to the refresh nonce, as in the starter scaffold), not fall through to zeros, an empty chart, or a "no data yet" message. A query that silently swallows its error makes real breakage (a missing table, an auth failure, a bad query) look like missing data. Reserve the empty state for a query that succeeded with no rows.
Date window
A data board owns its own date control — render Quill's `DateTimePicker` (never a custom Select or native date input) inside a `Popover` whose trigger is a Quill `Button`. `PopoverContent` gets exactly `className="w-auto p-0"` and nothing is added to `DateTimePicker` beyond `value`/`onApply`/`onCancel` (it self-sizes; don't pass `compact` or widths). Re-run every query when the window changes — see the `querying-canvas-data` skill for feeding it into `dateRange`.
Read more
name: building-react-quill-canvases description: > Author the React + Quill implementation of a PostHog canvas: the single-component contract, the allowed imports, Quill (PostHog's design system) component and composition rules, theme-aware design tokens, loading skeletons, and the in-canvas date picker. Use after building-canvases has routed a canvas request to a React implementation — dashboards, data boards, forms, tools, or any canvas that should look native to PostHog.
Building React + Quill canvases
The whole application is one React/TSX file (`src/canvas.tsx` in the source project). It must `export default` a single React component that takes no props — the host mounts it. Do not import react-dom or call createRoot.
Start from the working scaffold in [references/starter-scaffold.md](references/starter-scaffold.md) on a first build: it already wires the date picker, theme tokens, per-card skeletons, and correct typed-node result reading. Keep that wiring; replace the sample metric and layout.
Imports
Import only from: `react`, `react-dom`, `react-dom/client`, `@posthog/quill`, `recharts`, `lucide-react`, `dayjs`. Anything else — including dynamic `import()`, `require()`, `fetch()`, `<script>` tags, or remote code — fails validation. Use `@posthog/quill` for UI, `recharts` for charts, `lucide-react` for icons, `dayjs` for dates.
Quill component rules
A PostHog data board must be built entirely from `@posthog/quill` components — never a native control or a styled `<div>` standing in for one:
- Dropdown/picker → `Select` (never a native `<select>`); button → `Button` (never `<button>`);
text field → `Input`/`Textarea`; checkbox → `Checkbox`; label → `Label`.
- Table → `Table` (`TableHeader` > `TableRow` > `TableHead`, then `TableBody` > `TableRow` > `TableCell`);
panel → `Card` (`CardHeader` + `CardTitle` + `CardContent`); pill → `Badge`; titles → `Heading`; body → `Text`.
- The only non-Quill tags allowed are plain layout `<div>`s and `recharts` elements.
- Quill is built on Base UI: compose compound parts (`Select` + `SelectTrigger`/`SelectContent`/`SelectItem`),
use controlled `value` + `onValueChange`, and swap a part's element with the `render` prop (e.g. `<PopoverTrigger render={<Button …/>} />`) instead of wrapping it.
- Quill components are already themed — never restyle one with Tailwind classes or inline `style`;
use their `variant`/`size` props. Put layout utilities (`flex`, `grid`, `gap-4`, `p-4`) on your own wrapper `<div>`s.
- Buttons: default to `variant="outline"`; `variant="primary"` for the one main action only.
Styling and theme
- Style with Tailwind utilities and Quill components; reserve inline `style` for genuinely dynamic
runtime values (fixed sizes use arbitrary-value utilities like `h-[280px]`).
- The canvas follows the user's PostHog theme; a `.dark` class on the document root flips at runtime.
Color only from the design-token utilities — surfaces `bg-background bg-card bg-muted bg-primary bg-success bg-warning bg-info bg-destructive`; text `text-foreground text-muted-foreground text-card-foreground`; borders `border-border`. Never a hardcoded hex or light-only color.
- Status tokens invert the usual convention: the bare token (`bg-success`) is a pale background fill
and `-foreground` (`text-success-foreground`) is the strong readable color. Colored text or icons always use the `-foreground` utility; a filled pill pairs `bg-success text-success-foreground`. Prefer the Quill `Badge` (`variant="success"`/`"destructive"`) for deltas so you don't hand-pick.
- `bg-secondary`, `text-secondary`, `bg-accent`, and `bg-popover` are not defined in the canvas — avoid them.
- recharts strokes/fills use token CSS variables (`stroke="var(--primary)"`, grid/axes in
`var(--border)`/`var(--muted-foreground)`).
- Write Unicode glyphs (curly quotes, ellipsis, arrows, emoji) as literal characters in JSX —
`\uXXXX` escapes render verbatim in JSX text.
Loading, error, and empty states
Every data point renders a skeleton in its own `Card` while loading or refreshing: `SkeletonText` (matching `lines` and text-size `className`) for text/number values, `Skeleton` for blocks/charts. Drive `isLoading` off the data calls and set it true again on refresh; never show a blank or a jumping layout.
A failed query and an empty result are different states — never let one render as the other. `.catch` on every `ph.query`/`ph.loadInsight` must set an error state that renders visibly (the message plus a Retry button wired to the refresh nonce, as in the starter scaffold), not fall through to zeros, an empty chart, or a "no data yet" message. A query that silently swallows its error makes real breakage (a missing table, an auth failure, a bad query) look like missing data. Reserve the empty state for a query that succeeded with no rows.
Date window
A data board owns its own date control — render Quill's `DateTimePicker` (never a custom Select or native date input) inside a `Popover` whose trigger is a Quill `Button`. `PopoverContent` gets exactly `className="w-auto p-0"` and nothing is added to `DateTimePicker` beyond `value`/`onApply`/`onCancel` (it self-sizes; don't pass `compact` or widths). Re-run every query when the window changes — see the `querying-canvas-data` skill for feeding it into `dateRange`.
:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.
Repo: posthog/posthog
Other skills on posthog.
- /analyzing-expensive-users
Analyze the most expensive users in AI observability and explain why they cost so much. Use when the user asks about top spenders, expensive users, per-user LLM cost, user-level cost drivers, or patterns behind high AI observability spend.
Open skill - /creating-online-evaluations
Author continuously-running online evaluations in PostHog AI observability, grounded in real failure modes you've identified. Use when the user wants evaluations that automatically score new generations or whole traces going forward — "create an eval to catch X", "continuously
Open skill - /exploring-ai-failures
Find where an AI/LLM application is failing in production and surface the failure patterns, working from real traces. Use when someone wants to understand what's going wrong with an AI feature, find and categorize failure modes, triage errors, or investigate quality issues
Open skill - /exploring-llm-clusters
Investigate AI observability clusters — understand usage patterns in AI/LLM traffic, compare cluster behavior, compute cost/latency metrics, and drill into individual traces within clusters.
Open skill - /exploring-llm-costs
Investigate LLM spend in PostHog — total cost over time, cost by model, provider, user, trace, or custom dimension, token and cache-hit economics, and cost regressions. Use when the user asks "how much are we spending on LLMs?", "which model / user / feature is most expensive?",
Open skill - /exploring-llm-evaluations
Investigate AI observability evaluations — `hog` (deterministic code-based), `llm_judge` (LLM-prompt-based), and `sentiment` (user-message sentiment). Find existing evaluations, inspect their configuration, run them against specific generations, query individual results, and
Open skill

