Skip to content

ContextMenu

oldmangan edited this page Oct 7, 2026 · 3 revisions

A right-click menu with icons, shortcut hints, danger and disabled items, separators and submenus. It isn't mounted in a cell: it opens at the pointer.

from wapyt import ContextMenu, ContextMenuConfig, MenuItem

menu = ContextMenu(ContextMenuConfig(items=[
    MenuItem("open", "Open", "mdi-open-in-new", shortcut="Enter"),
    MenuItem("rename", "Rename", "mdi-pencil", shortcut="F2"),
    MenuItem("share", "Share", "mdi-share-variant", items=[
        MenuItem("copy_link", "Copy link", "mdi-link"),
        MenuItem("email", "Email…", "mdi-email-outline"),
    ]),
    MenuItem(separator=True),
    MenuItem("delete", "Delete", "mdi-delete", danger=True, shortcut="Del"),
], label="File"))

menu.attach("#files", context="files")
menu.on_select(lambda p: self.on_file_action(p["id"], p["target"]))
<ul id="files">
  <li data-context="r1">report.pdf</li>
  <li data-context="r2">photo.png</li>
</ul>

Items

MenuItem(id, label, icon, shortcut, danger, disabled, items, separator, checkable, group, checked)

Field
id Reported by on_select; used by hide / disable. Unique across the whole menu, submenus included
label, icon Text (defaults to the id) and an MDI class
shortcut A hint on the right ("Ctrl+C"). Display only: the menu doesn't bind the key
danger Destructive styling
disabled Shown but not choosable
items A submenu; the item opens it instead of being chosen
separator=True A divider (every other field is ignored)
checkable, checked A tick that flips when chosen (role="menuitemcheckbox")
group, checked One of a radio group: choosing it checks it and unchecks the others (role="menuitemradio")

Separators that would lead, trail or double up after hiding items are dropped automatically.

Opening it

attach(target, context=None) opens the menu over an element (a CSS selector or element) on right-click, Shift+F10 or the Menu key. context comes back in every payload. target in the payload is the data-context attribute of the nearest ancestor of the click that has one, so one menu attached to a list knows which row was clicked. detach(target) removes it.

show_at(x, y, context=None, target=None, hide=[], disable=[]) opens it at viewport coordinates from your own handler, e.g. event.clientX / event.clientY in a contextmenu listener. hide and disable list item ids to drop or grey out for this opening only, for per-row permissions:

def on_row_menu(self, event, row):
    event.preventDefault()
    self.menu.show_at(event.clientX, event.clientY, context=row["id"],
                      hide=[] if row["can_share"] else ["share"],
                      disable=["delete"] if row["locked"] else [])

Only one menu is open on a page at a time; opening another closes it.

Events

Method Payload
on_select {id, context, target}, plus checked (the new state) for checkable and group items
on_show {context, target}
on_hide {context, target}, whether chosen or dismissed

Keyboard

Following the WAI-ARIA menu pattern:

  • Focus moves into the menu when it opens.
  • ↑/↓ move and wrap; Home and End jump to the ends.
  • Enter or Space chooses.
  • → opens a submenu and ← closes it.
  • Escape closes the innermost menu; Tab closes everything.
  • Focus returns to where it was.

Disabled items can be focused but not chosen.

Clicking outside, scrolling or resizing the window also closes the menu. Its position is kept inside the viewport, and submenus open to the left when there's no room on the right.

Other methods

hide(), is_open(), set_items(items), destroy().

Item state

Added in #44. Unlike show_at(hide=, disable=), which applies to one opening, these persist:

Method
set_disabled(ids, disabled=True) False enables items, even ones declared disabled
set_hidden(ids, hidden=True)
set_checked(id, checked=True) Without firing on_select; checking a group item unchecks the rest
is_disabled, is_hidden, is_checked

For a menu bar across the top of the app, see MenuBar.

Tree, DataTable and Terminal use ContextMenu for their right-click menus, keeping TreeAction / TableAction as their API.

ContextMenuConfig(extra={"menuClass": "my-menu", "itemClass": "my-item"}) adds class names beside the wapyt-cmenu ones, e.g. for test selectors or your own CSS. (The built-in widgets also set per-item data-* attributes from JavaScript; MenuItem doesn't expose that.)

Clone this wiki locally