Context menu component triggered by right-click (contextmenu browser event).
A context menu provides a contextual set of actions for a specific element — just like the browser's native right-click menu, but fully customisable and integrated with your LiveView. Common use cases include file managers, spreadsheets, canvas editors, data grids, and any UI where per-item actions should be discoverable on right-click.
Requires the PhiaContextMenu JavaScript hook registered in app.js. The
hook intercepts the contextmenu event on the trigger area, prevents the
native browser menu, and positions the custom panel at the exact cursor
coordinates.
Hook Registration
Copy the hook via mix phia.add context_menu, then register it:
# assets/js/app.js
import PhiaContextMenu from "./hooks/context_menu"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { PhiaContextMenu }
})Hook Behaviour
- Right-clicking inside
context_menu_trigger/1opens the menu at the cursor - The native browser context menu is suppressed
- Smart viewport-aware positioning: the panel flips if it would overflow the viewport edge (right→left, bottom→top)
- Clicking outside the open panel or pressing
Escapecloses it ArrowUp/ArrowDownnavigate between items;Enteractivates the focused item- Only one context menu can be open at a time — opening a new one closes the previous
Sub-components
| Function | Element | Purpose |
|---|---|---|
context_menu/1 | div | Root container |
context_menu_trigger/1 | div | Right-clickable area with hook anchor |
context_menu_content/1 | div | Floating panel (positioned fixed by hook) |
context_menu_item/1 | div | Clickable item (role="menuitem") |
context_menu_separator/1 | hr | Visual divider between item groups |
context_menu_checkbox_item/1 | div | Toggleable item (role="menuitemcheckbox") |
context_menu_label/1 | div | Non-interactive section heading |
Example — File Manager
A classic context menu for file operations:
<.context_menu id="file-ctx">
<.context_menu_trigger context_menu_id="file-ctx">
<div class="flex items-center gap-2 p-3 rounded-md hover:bg-muted cursor-default">
<.icon name="file" />
{file.name}
</div>
</.context_menu_trigger>
<.context_menu_content id="file-ctx-content">
<.context_menu_label>{@file.name}</.context_menu_label>
<.context_menu_separator />
<.context_menu_item phx-click="open_file" phx-value-id={@file.id}>
Open
</.context_menu_item>
<.context_menu_item phx-click="rename_file" phx-value-id={@file.id}>
Rename...
</.context_menu_item>
<.context_menu_item phx-click="duplicate_file" phx-value-id={@file.id}>
Duplicate
</.context_menu_item>
<.context_menu_separator />
<.context_menu_checkbox_item
checked={@file.starred}
phx-click="toggle_star"
phx-value-id={@file.id}>
Starred
</.context_menu_checkbox_item>
<.context_menu_separator />
<.context_menu_item phx-click="delete_file" phx-value-id={@file.id}
class="text-destructive focus:text-destructive">
Move to Trash
</.context_menu_item>
</.context_menu_content>
</.context_menu>Example — Data Grid Row
Context menus work well on table rows in data grids:
<tr :for={row <- @rows}>
<.context_menu id={"row-ctx-#{row.id}"}>
<.context_menu_trigger context_menu_id={"row-ctx-#{row.id}"}>
<td class="px-4 py-2">{row.name}</td>
<td class="px-4 py-2">{row.status}</td>
</.context_menu_trigger>
<.context_menu_content id={"row-ctx-#{row.id}-content"}>
<.context_menu_item phx-click="view_row" phx-value-id={row.id}>View</.context_menu_item>
<.context_menu_item phx-click="edit_row" phx-value-id={row.id}>Edit</.context_menu_item>
<.context_menu_separator />
<.context_menu_item phx-click="archive_row" phx-value-id={row.id}>Archive</.context_menu_item>
</.context_menu_content>
</.context_menu>
</tr>Example — Canvas / Drawing Area
For design tools where the trigger is the entire canvas:
<.context_menu id="canvas-ctx">
<.context_menu_trigger context_menu_id="canvas-ctx">
<div id="drawing-canvas" class="w-full h-96 bg-muted rounded border">
<%# canvas content %>
</div>
</.context_menu_trigger>
<.context_menu_content id="canvas-ctx-content">
<.context_menu_item phx-click="paste">Paste</.context_menu_item>
<.context_menu_item phx-click="select_all">Select All</.context_menu_item>
<.context_menu_separator />
<.context_menu_label>View</.context_menu_label>
<.context_menu_checkbox_item checked={@show_grid} phx-click="toggle_grid">
Show Grid
</.context_menu_checkbox_item>
<.context_menu_checkbox_item checked={@snap_to_grid} phx-click="toggle_snap">
Snap to Grid
</.context_menu_checkbox_item>
</.context_menu_content>
</.context_menu>Accessibility
context_menu_trigger/1hasaria-haspopup="menu"to signal the right-click behaviour to assistive technologycontext_menu_content/1hasrole="menu"andaria-orientation="vertical"- Items have
role="menuitem"(ormenuitemcheckboxfor toggleable items) - All items have
tabindex="-1"so keyboard navigation is hook-managed via[data-disabled]andfocus()calls, matching the WAI-ARIA menu pattern - The panel is
position: fixed(not absolute) so it appears at the cursor coordinates regardless of the containing element's scroll position oroverflowsetting
Summary
Functions
Renders the context menu root container.
Renders a toggleable context menu item with a check mark indicator.
Renders the context menu content panel.
Renders a context menu item with role="menuitem".
Renders a non-interactive section heading inside the context menu.
Renders a horizontal visual separator between groups of context menu items.
Renders the context menu trigger area.