posthog/scene-menu-bar
Conventions for adding or editing the SceneMenuBar above a scene's <SceneTitleSection>. Use when adding new menu items, moving items between menus, gating behind feature flags, building a new scene's menubar, or wiring rich inputs (tags, combobox) into the bar.
npx skills add https://github.com/PostHog/posthog --skill scene-menu-bar
PostHog scenes render a Mac-style menu bar above <SceneTitleSection> that consolidates
ScenePanel actions into a discoverable, keyboard-navigable surface. This skill documents
the taxonomy, the available primitives, and the wiring rules.
The whole bar is gated by the SCENE_MENU_BAR feature flag (see lib/constants.tsx).
All primitives live in frontend/src/layout/scenes/components/SceneMenuBar.tsx.
| Component | Use for |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <SceneMenuBar> | Top-level wrapper; renders above <SceneTitleSection>. Includes the universal right cluster (PostHog AI / Docs / Support). |
| <SceneMenuBarMenu label> | Top-level menu (File / Edit / View / Metadata / Staff only). |
| <SceneMenuBarSubMenu label> | Nested sub-menu inside a SceneMenuBarMenu (e.g. Create, Export, Add to notebook). Auto-prepends a blank icon slot to the trigger so the label aligns with icon-bearing siblings — pass withIconBlank={false} to opt out. |
| <SceneMenuBarItem> | Standard menu item (action / navigation). |
| <SceneMenuBarCheckboxItem> | Toggle item — reflects boolean state via checkmark. |
| <SceneMenuBarRadioGroup> + <SceneMenuBarRadioItem> | One-of-many mutually-exclusive options. |
| <SceneMenuBarSeparator> | Horizontal divider between groups. |
| <SceneMenuBarShortcut> | Right-aligned keyboard shortcut hint. |
| <SceneMenuBarPopover label> | Drop-in alternative to SceneMenuBarMenu when content needs rich form controls (text inputs, comboboxes). Uses Popover under the hood — Menu.Popup intercepts keystrokes and blocks inputs. Trigger does NOT participate in CompositeRoot keyboard nav. |
| Menu | Items |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| File | + Create sub-menu (top), ── , project tree items (Open / Move / Star), Copy to another project, Manage with Terraform, scene-specific file ops, Export sub-menu, ──, Delete / Archive / Restore (destructive at the very bottom) |
| Edit | Duplicate, Rename, Edit in SQL editor, scene-specific edits, state mutations (Pause/Resume, Activate/Deactivate), ──, toggle group (checkbox/radio items, separated by <SceneMenuBarSeparator> from regular items) |
| View _(conditional)_ | Cross-resource viewing — View recordings, View metalytics, View related insights, etc. Anything that navigates to _see_ something tangentially related to the current resource. Skip when empty. |
| Metadata _(use SceneMenuBarPopover)_ | Tags, Evaluation contexts, Stage, Activity indicator, External references, Comments, etc. |
| Staff only _(conditional)_ | Debug panels, staff-only toggles. Only render for staff / superpowers / impersonated / non-cloud users. |
> State lives in Edit. Pause/Resume, Activate/Deactivate, Favorite/Pin toggles and similar
> state mutations all live inside the Edit menu — usually at the bottom, after a
> <SceneMenuBarSeparator> if there's a toggle group. Don't create a separate State menu.
Right cluster is fixed and universal: PostHog AI · Docs · Support. Do not add to it.
<SceneMenuBarSeparator>.variant="destructive" for visual treatment (red text + icon).opensFloatingUi to destructive items even when they open a confirmation dialog — the destructive variant + clear label are signal enough.<AccessControlAction> where the resource has access control levels.<SceneMenuBarCheckboxItem> and <SceneMenuBarRadioGroup> items together.<SceneMenuBarSeparator> above and (if more items follow) below.Pinned not Pin/Unpin, Show debug panel not Show/Hide debug panel.<SceneMenuBarRadioGroup value onValueChange>).opensFloatingUi propAppend … to the item label as a Mac-style affordance for items that open additional floating UI:
+ Create cross-sells<SceneMenuBarSubMenu label="Create"> at the top of File.<SceneMenuBarSeparator> between Create and the rest of File.SceneMenuBarMenu auto-disables its trigger when its children render nothing at compile
time — e.g. {false && <Item/>}, {null}, or an empty fragment. The trigger stays
visible (greyed out, cursor-not-allowed) so the menu set still communicates the bar's
capabilities to the user.
For menus whose children may render null at runtime (the most common case is
<SceneMenuBarFileItems>, which returns null when no project-tree entry is registered),
the parent cannot detect that the popup will be empty. You must either:
disabled explicitly based on the same value the children depend on, e.g.: const { projectTreeRefEntry } = useValues(projectTreeDataLogic)
const hasFileItems = !!projectTreeRefEntry
<SceneMenuBarMenu label="File" disabled={!hasFileItems}>
{hasFileItems && <SceneMenuBarFileItems dataAttrKey="…" />}
</SceneMenuBarMenu>
{
hasAnyEditItem && <SceneMenuBarMenu label="Edit">…</SceneMenuBarMenu>
}
Prefer (1) when the menu's presence is part of the scene's identity (File should always
be visible even if disabled), and (2) when the menu is genuinely optional (View, Staff
only).
<SceneMenuBarPopover>, not a <SceneMenuBarMenu>. Menu popupsintercept keystrokes (typeahead, arrow nav) and prevent text inputs from receiving
input.
<TagsCombobox> (lib/components/Scenes/TagsCombobox.tsx) for multi-select chipinputs — selection-only by default; pass allowCustomValues to surface a
"Create new {noun} '...'" item at the bottom.
<SceneTagsCombobox> (lib/components/Scenes/SceneTagsCombobox.tsx) as theready-made wrapper that swaps in for <SceneTags> under SCENE_MENU_BAR.
When autosaving from menubar inputs (tags, toggles), use a kea listener with:
setX + updateX) immediately, before the API call.await breakpoint(250) to debounce rapid changes.breakpoint() after the API response to bail if a newer call has superseded this one.when the server returns differently-ordered values.
error.isBreakpoint so kea swallows it silently.See saveTagsInline in frontend/src/scenes/feature-flags/featureFlagLogic.ts.
Use <SceneMenuBarFileItems dataAttrKey={RESOURCE_TYPE} /> at the bottom of File
(before the destructive separator) to render Open in project tree, Move to folder,
Add/Remove starred. It auto-hides when no project-tree entry is registered.
Lives outside the <Menubar> wrapper to keep CompositeRoot keyboard nav working for
the actual menus. Do not add items here without UX review.
<SceneMenuBar> reads sceneLayoutLogic.sceneLayoutConfig.layout and bleeds past the
scene container's padding (-mx-4 -mt-4) only when the layout is padded:
app, app-container, or app-full-scene-height. For unpadded layouts (app-raw,
app-raw-no-header, plain) the negatives would overshoot and visually break the
header, so they're skipped. You don't need to do anything in your scene — just register
the right SceneConfig.layout and the bar adapts. Sentinel: a data-scene-layout
attribute is set on the wrapper for debugging.
frontend/src/scenes/feature-flags/FeatureFlag.tsxfrontend/src/scenes/insights/SidePanel/InsightSceneMenuBar.tsxfrontend/src/scenes/dashboard/DashboardSceneMenuBar.tsxWhen migrating a new scene, follow the <Scene>SceneMenuBar.tsx pattern (component
that reads featureFlagLogic, early-returns null if the flag is off, otherwise renders
the bar). Mount it directly above <SceneTitleSection>.
When migrating a scene, take its existing ScenePanel and map every item to:
variant="destructive" (delete/archive/remove).opensFloatingUi (opens modal/popover/dialog/side panel).SceneMenuBarCheckboxItem).See /Users/adamleithp/Desktop/scene-menu-bar-grouping.md for the running inventory of
all ScenePanel items across PostHog and their proposed menu placement.
Take posthog/scene-menu-bar 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.