Components Context menu

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Context menu

Right-click, long-press or Shift+F10, opened at the pointer. A real menu with keyboard support, not a dropdown wearing a costume.
Right-click a file

The classic: an object on the page with its own commands. Right-click the card (or long-press it on a phone) and the menu opens at the pointer, clamped so it never runs off an edge. Keyboard users tab to the card and press Shift+F10. Items carry icons, a kbd hint and the danger variant for the one that deletes something.

heex
<.context_menu id="showcase-context-menu-file" class="max-w-xs">
<:trigger>
<div class="flex items-center gap-3 p-4 bg-white border border-gray-200 rounded-xl dark:bg-gray-900 dark:border-gray-800">
<.icon name="hero-document-chart-bar" class="w-8 h-8 text-gray-400 shrink-0" />
<div class="min-w-0">
<div class="text-sm font-medium truncate">Q3-forecast.xlsx</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Edited 2 days ago</div>
</div>
</div>
</:trigger>
<.context_menu_label>Q3-forecast.xlsx</.context_menu_label>
<.context_menu_item link_type="button" kbd="↵">
<.icon name="hero-arrow-top-right-on-square" class="w-4 h-4" /> Open
</.context_menu_item>
<.context_menu_item link_type="button" kbd="F2">
<.icon name="hero-pencil-square" class="w-4 h-4" /> Rename
</.context_menu_item>
<.context_menu_item link_type="button" kbd="⌘D">
<.icon name="hero-square-2-stack" class="w-4 h-4" /> Duplicate
</.context_menu_item>
<.context_menu_item link_type="button" disabled>
<.icon name="hero-lock-closed" class="w-4 h-4" /> Move to team folder
</.context_menu_item>
<.context_menu_separator />
<.context_menu_item link_type="button" variant="danger" kbd="⌘⌫">
<.icon name="hero-trash" class="w-4 h-4" /> Delete
</.context_menu_item>
</.context_menu>
Right-click content

The other half of the pattern: commands about what you are reading rather than an object you can point at. Same panel, same items, a trigger region that happens to be prose.

heex
<.context_menu id="showcase-context-menu-text" class="max-w-md">
<:trigger>
<p class="p-4 text-sm leading-relaxed text-gray-600 border border-gray-200 border-dashed rounded-xl dark:text-gray-300 dark:border-gray-700">
Phoenix ships with LiveView, so most of what people reach for a
single-page framework to do is already in the box. Right-click
anywhere in this paragraph.
</p>
</:trigger>
<.context_menu_item link_type="button" kbd="⌘C">
<.icon name="hero-clipboard" class="w-4 h-4" /> Copy
</.context_menu_item>
<.context_menu_item link_type="button" kbd="⌘⇧H">
<.icon name="hero-paint-brush" class="w-4 h-4" /> Highlight
</.context_menu_item>
<.context_menu_separator />
<.context_menu_item link_type="a" to="#share">
<.icon name="hero-share" class="w-4 h-4" /> Share
</.context_menu_item>
<.context_menu_item link_type="a" to="#search">
<.icon name="hero-magnifying-glass" class="w-4 h-4" /> Search the web
</.context_menu_item>
</.context_menu>
Properties
<.context_menu>
Attribute Type Default Description
class any nil extra classes for the trigger region wrapper
disabled boolean false disables the context menu entirely; right-click falls through to the browser default menu
id string the component id; autogenerated if not set
menu_class any nil extra classes for the menu panel
rest global
:inner_block slot slot the menu items
:trigger slot slot the right-clickable region. Rendered as a block wrapper, so put anything inside - a card, a table row, a text area
<.context_menu_item>
Attribute Type Default Description
class any nil any additional CSS classes
disabled boolean false greys the item out and takes it off the arrows
kbd string nil right-aligned keyboard shortcut hint, e.g. "⌘D"
label string nil the item label; or pass content in the inner block
link_type string "button" what the item renders as
one of: "a", "live_patch", "live_redirect", "button"
rest global
to string nil link path
variant string "default" danger renders the destructive treatment (danger ramp text, danger hover surface)
one of: "default", "danger"
:inner_block slot slot the item content - text, an icon, whatever the row needs
<.context_menu_label>
Attribute Type Default Description
class any nil any additional CSS classes
rest global
:inner_block slot slot the heading text
<.context_menu_separator>
Attribute Type Default Description
class any nil any additional CSS classes
rest global