
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.
SKILL.md
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 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, thenTableBody>TableRow>TableCell); panel →Card(CardHeader+CardTitle+CardContent); pill →Badge; titles →Heading; body →Text. - The only non-Quill tags allowed are plain layout
<div>s andrechartselements. - Quill is built on Base UI: compose compound parts (
Select+SelectTrigger/SelectContent/SelectItem), use controlledvalue+onValueChange, and swap a part's element with therenderprop (e.g.<PopoverTrigger render={<Button …/>} />) instead of wrapping it. - Quill components are already themed — never restyle one with Tailwind classes or inline
style; use theirvariant/sizeprops. 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
stylefor genuinely dynamic runtime values (fixed sizes use arbitrary-value utilities likeh-[280px]). - The canvas follows the user's PostHog theme; a
.darkclass on the document root flips at runtime. Color only from the design-token utilities — surfacesbg-background bg-card bg-muted bg-primary bg-success bg-warning bg-info bg-destructive; texttext-foreground text-muted-foreground text-card-foreground; bordersborder-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-foregroundutility; a filled pill pairsbg-success text-success-foreground. Prefer the QuillBadge(variant="success"/"destructive") for deltas so you don't hand-pick. bg-secondary,text-secondary,bg-accent, andbg-popoverare not defined in the canvas — avoid them.- recharts strokes/fills use token CSS variables (
stroke="var(--primary)", grid/axes invar(--border)/var(--muted-foreground)). - Write Unicode glyphs (curly quotes, ellipsis, arrows, emoji) as literal characters in JSX —
\uXXXXescapes 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.
More skills from the posthog repository
View all 74 skillsanalyzing-expensive-users
analyze expensive users in AI observability
Jul 28AnalyticsCost OptimizationObservabilityPostHogauditing-endpoints
audit PostHog project endpoints
Jun 8AnalyticsAuditPostHogauditing-warehouse-source-health
audit PostHog data warehouse source health
Jun 18AuditData WarehouseObservabilityPostHogauditing-warehouse-view-health
audit PostHog materialized view health
Jun 18AuditData WarehousePerformancePostHogauthoring-error-tracking-alerts
author PostHog error tracking alerts
Jun 18AlertingDebuggingObservabilityPostHogauthoring-log-alerts
author log alerts in PostHog
Jul 18AnalyticsMonitoringObservabilityOperations +1
More from PostHog
View publisherbuilding-canvases
create and edit PostHog canvases
posthog
Aug 6AutomationDesignPostHogPrototypingbuilding-html-canvases
author HTML and CSS PostHog canvases
posthog
Aug 6CSSDesignGraphicsHTML +1building-workflows
build and edit PostHog workflows
posthog
Aug 6AutomationMCPPostHogWorkflow Automationcheck-posthog-loading
inspect PostHog SDK loading across URLs
posthog
May 7AnalyticsDebuggingFrontendObservability +1consuming-endpoints-from-client-code
integrate PostHog endpoints into client applications
posthog
Jun 8API DevelopmentFrontendPostHogSDK