Skip to content

Guide Recipes ContextMenu

MeowLynxSea edited this page Jun 18, 2026 · 4 revisions

Recipe: Context menu on a list row

Right-click context menu using popover + right-click on a list row. For a click-triggered dropdown, use dropdown_menu instead.

Building blocks

  • popover — anchors content next to its trigger. Owns Entity<PopoverState> with open() / close() / toggle(). The renderer floats the content beside the trigger when state.is_open().
  • menu — body-only shell that renders one row per DropdownItem. Owns Entity<MenuState> with set_items(...) and set_on_select(...).
  • Right-click on a div.on_mouse_down(MouseButton::Right, …).

Example

use gpui::MouseButton;
use yororen_ui::headless::dropdown_menu::{DropdownItem, DropdownMenuItem};
use yororen_ui::headless::list_item::list_item;
use yororen_ui::headless::menu::{menu, MenuState};
use yororen_ui::headless::popover::{popover, PopoverState};

pub struct RowWithMenu {
    pub row_label:     String,
    pub popover_state: gpui::Entity<PopoverState>,
    pub menu_state:    gpui::Entity<MenuState>,
}

impl Render for RowWithMenu {
    fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let popover_state = self.popover_state.clone();
        let menu_state = self.menu_state.clone();
        let popover_for_close = popover_state.clone();

        menu_state.update(cx, |s, _| {
            s.set_items(vec![
                DropdownItem::Item(DropdownMenuItem::new("open", "Open")),
                DropdownItem::Item(DropdownMenuItem::new("rename", "Rename")),
                DropdownItem::Separator,
                DropdownItem::Item(DropdownMenuItem::new("delete", "Delete")),
            ]);
            s.set_on_select(move |id: SharedString, _w, cx| {
                match id.as_str() {
                    "open"   => { /* … */ }
                    "rename" => { /* … */ }
                    "delete" => { /* … */ }
                    _ => {}
                }
                popover_for_close.update(cx, |s, _| s.close());
            });
        });

        let popover_for_open = popover_state.clone();
        let row = list_item("row-content", self.row_label.clone(), cx)
            .render(cx)
            .on_mouse_down(MouseButton::Right, move |_ev, _w, cx| {
                popover_for_open.update(cx, |s, _| s.toggle());
            });

        let content = menu("row-menu", menu_state.clone()).render(cx);

        popover("row-menu", popover_state)
            .trigger(row.into_any_element())
            .content(content)
            .render(cx)
    }
}

Alternative: click-triggered dropdown_menu

If you want a click-triggered menu ("⋯" button on a row), use dropdown_menu directly:

use yororen_ui::headless::button::button;
use yororen_ui::headless::dropdown_menu::{
    DropdownItem, DropdownMenuItem, DropdownMenuState, dropdown_menu,
};

let trigger = button("row-trigger", cx).render(cx).child("⋯");

dropdown_menu("row-dropdown", menu_state)
    .trigger(trigger.into_any_element())
    .render(cx)

Use popover + right-click when the trigger is a list row; use dropdown_menu when the trigger is a button.

Notes

  • The popover's dismiss_on_outside_click and dismiss_on_escape are both true by default.
  • set_on_select is Fn(SharedString, &mut Window, &mut App) — the id comes in as SharedString. Use id.as_str() to compare against literals.
  • DropdownItem::Item(DropdownMenuItem::new(id, label)) for items, DropdownItem::Separator for dividers.

See also

Clone this wiki locally