Skip to main content
Built-in detail pages render <PageHeader> at the top with primary action buttons on the right and a more-actions () dropdown next to them. Both are slot-based — you can inject your own buttons or menu items without touching the host page. Two slots cover the surface:
  • page.actions — the right-aligned action zone (buttons, badges)
  • page.actions_dropdown — the dropdown menu
This recipe shows both — and how to gate them by permission + resource state. (Strings are hardcoded for brevity; real buttons should go through i18n — see Translations.)

A button in the actions row

Register it:
Two layers of gating, both inside the component:
  1. Permission checkusePermissions(); render null when the user can’t act.
  2. Resource-state check — the user can send invoices, but this specific order isn’t ready yet.
(The slot entry’s if predicate receives only the slot’s own context — { resource } here — so use it for resource-state conditions, and keep permission checks in the component. See Slots.)

A dropdown menu item

For secondary actions, use page.actions_dropdown and render <DropdownMenuItem>:
The event.preventDefault() keeps the dropdown open if the mutation fails — without it, the menu closes on click and the toast appears with no context.

Confirm before acting

For destructive or expensive actions, wrap the click in a <Dialog>:

Context

The resource prop is whatever the host page passed to <PageHeader resource={…} />. On the order detail page it’s an Order; on a product page it’s a Product. The slot doesn’t statically know which — see Slots for the typing escape hatches. For the current store, user, or permissions, call the hooks inside your component — useStore(), useAuth(), usePermissions() from @spree/dashboard-core.

Reference