Skip to content
Data
Skill

/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

From plugin
posthog
38k156 skills11 agents1 command2 MCP
Install
$ npx -y skills add posthog/posthog --skill building-react-quill-canvases --agent claude-code

How 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.md
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`.

Read more
Ships withposthog

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

Get the whole plugin

Other skills on posthog.